diff options
author | 2015-09-13 08:41:44 +0200 | |
---|---|---|
committer | 2015-09-13 08:41:44 +0200 | |
commit | f88cefad27a1737c76e09d99c9fb43e173506081 (patch) | |
tree | 2d8167d03579c46e226471747eb4407bd00ed6fa /src/devices/cpu/tms9900 | |
parent | e92ac9e0fa8e99869894bea00589bbb526be30aa (diff) |
Move all devices into separate part of src tree (nw)
Diffstat (limited to 'src/devices/cpu/tms9900')
-rw-r--r-- | src/devices/cpu/tms9900/9900dasm.c | 812 | ||||
-rw-r--r-- | src/devices/cpu/tms9900/99xxcore.h | 5319 | ||||
-rw-r--r-- | src/devices/cpu/tms9900/ti990_10.c | 148 | ||||
-rw-r--r-- | src/devices/cpu/tms9900/ti990_10.h | 61 | ||||
-rw-r--r-- | src/devices/cpu/tms9900/tms9900.c | 2736 | ||||
-rw-r--r-- | src/devices/cpu/tms9900/tms9900.h | 403 | ||||
-rw-r--r-- | src/devices/cpu/tms9900/tms9980a.c | 300 | ||||
-rw-r--r-- | src/devices/cpu/tms9900/tms9980a.h | 56 | ||||
-rw-r--r-- | src/devices/cpu/tms9900/tms9995.c | 3466 | ||||
-rw-r--r-- | src/devices/cpu/tms9900/tms9995.h | 446 | ||||
-rw-r--r-- | src/devices/cpu/tms9900/tms99com.h | 93 |
11 files changed, 13840 insertions, 0 deletions
diff --git a/src/devices/cpu/tms9900/9900dasm.c b/src/devices/cpu/tms9900/9900dasm.c new file mode 100644 index 00000000000..7b3153e6c4c --- /dev/null +++ b/src/devices/cpu/tms9900/9900dasm.c @@ -0,0 +1,812 @@ +// license:BSD-3-Clause +// copyright-holders:Raphael Nabet +/***************************************************************************** + * + * 9900dasm.c + * TMS 9900 family disassembler + * + * Copyright Raphael Nabet + * Based on previous work Copyright John Butler. + * Based on 6502dasm.c 6502/65c02/6510 disassembler by Juergen Buchmueller + * + * - This source code is released as freeware for non-commercial purposes. + * - You are free to use and redistribute this code in modified or + * unmodified form, provided you list me in the credits. + * - If you modify this source code, you must add a notice to each modified + * source file that it has been changed. If you're a nice person, you + * will clearly mark each change too. :) + * - The author of this copywritten work reserves the right to change the + * terms of its usage and license at any time, including retroactively + * - This entire notice must remain in the source code. + * + *****************************************************************************/ + + +#include "emu.h" +#include "debugger.h" +#include "tms9900.h" + +#define MASK 0x0000ffff +#define BITS(val,n1,n2) ((val>>(15-(n2))) & (MASK>>(15-((n2)-(n1))))) + +enum format_t +{ + format_1, /* 2 address instructions */ + format_2a, /* jump instructions */ + format_2b, /* bit I/O instructions */ + format_3_9, /* logical, multiply, and divide instructions */ + format_4, /* CRU instructions */ + format_5, /* register shift instructions */ + format_6, /* single address instructions */ + format_7, /* instructions without operands */ + format_8a, /* immediate instructions (destination register) */ + format_8b, /* immediate instructions (no destination register) */ + format_9, /* extended operation instruction */ + format_10, /* memory map file instruction */ + format_11, /* multiple precision instructions */ + format_12, /* string instructions */ + format_13, /* multiple precision shift instructions */ + format_14, /* bit testing instructions */ + format_15, /* invert order of field instruction */ + format_16, /* field instructions */ + format_17, /* alter register and jump instructions */ + format_18, /* single register operand instructions */ + format_liim,/* format for liim (looks like format 18) */ + format_19, /* move address instruction */ + format_20, /* list search instructions */ + format_21, /* extend precision instruction */ + + illegal +}; + +/* definitions for flags */ +enum +{ + /* processor set on which opcodes are available */ + ps_any = 0x01, /* every processor in the tms9900/ti990 family */ + ps_mapper = 0x02, /* processors with memory mapper (ti990/10, ti990/12, + and tms99000 with mapper coprocessor) */ + ps_tms9995 = 0x04, /* ti990/12, tms9995, and later */ + ps_tms99000 = 0x08, /* ti990/12, tms99000, and later */ + ps_ti990_12 = 0x10, /* ti990/12 only */ + + /* additional flags for special decoding */ + sd_11 = 0x100, /* bit 11 should be cleared in li, ai, andi, ori, ci, stwp, stst */ + sd_11_15 = 0x200 /* bits 11-15 should be cleared in lwpi, limi, idle, rset, rtwp, ckon, ckof, lrex */ +}; + +struct description_t +{ + const char *mnemonic; + format_t format; + int flags; +}; + + +enum opcodes { + /* basic instruction set */ + _a=0, _ab, _c, _cb, _s, _sb, _soc, _socb, _szc, _szcb, + _mov, _movb, _coc, _czc, _xor, _mpy, _div, _xop, _b, _bl, + _blwp, _clr, _seto, _inv, _neg, _abs, _swpb, _inc, _inct, _dec, + _dect, _x, _ldcr, _stcr, _sbo, _sbz, _tb, _jeq, _jgt, _jh, + _jhe, _jl, _jle, _jlt, _jmp, _jnc, _jne, _jno, _joc, _jop, + _sla, _sra, _src, _srl, _ai, _andi, _ci, _li, _ori, _lwpi, + _limi, _stst, _stwp, _rtwp, _idle, _rset, _ckof, _ckon, _lrex, + + /* mapper instruction set */ + _lds, _ldd, _lmf, + + /* tms9995 instruction set */ + _divs, _mpys, _lst, _lwp, + + /* tms99000 instruction set */ + _bind, + + /* ti990/12 instruction set */ + _sram, _slam, _rto, _lto, _cnto, _slsl, _slsp, _bdc, _dbc, _swpm, + _xorm, _orm, _andm, _sm, _am, _mova, _emd, _eint, _dint, _stpc, + _cs, _seqb, _movs, _lim, _lcs, _blsk, _mvsr, _mvsk, _pops, _pshs, + + _cri, _cdi, _negr, _negd, _cre, _cde, _cer, _ced, _nrm, _tmb, + _tcmb, _tsmb, _srj, _arj, _xit, _insf, _xv, _xf, _ar, _cir, + _sr, _mr, _dr, _lr, _str, _iof, _sneb, _crc, _ts, _ad, + _cid, _sd, _md, _dd, _ld, _std, _ep, + + /* tms9940-only instruction set */ + _liim, _dca, _dcs, + + _ill +}; + + +static const description_t descriptions[144+3+1] = +{ + /* basic instruction set */ + { "a", format_1, ps_any }, { "ab", format_1, ps_any }, + { "c", format_1, ps_any }, { "cb", format_1, ps_any }, + { "s", format_1, ps_any }, { "sb", format_1, ps_any }, + { "soc", format_1, ps_any }, { "socb", format_1, ps_any }, + { "szc", format_1, ps_any }, { "szcb", format_1, ps_any }, + { "mov", format_1, ps_any }, { "movb", format_1, ps_any }, + { "coc", format_3_9, ps_any }, { "czc", format_3_9, ps_any }, + { "xor", format_3_9, ps_any }, { "mpy", format_3_9, ps_any }, + { "div", format_3_9, ps_any }, { "xop", format_9, ps_any }, + { "b", format_6, ps_any }, { "bl", format_6, ps_any }, + { "blwp", format_6, ps_any }, { "clr", format_6, ps_any }, + { "seto", format_6, ps_any }, { "inv", format_6, ps_any }, + { "neg", format_6, ps_any }, { "abs", format_6, ps_any }, + { "swpb", format_6, ps_any }, { "inc", format_6, ps_any }, + { "inct", format_6, ps_any }, { "dec", format_6, ps_any }, + { "dect", format_6, ps_any }, { "x", format_6, ps_any }, + { "ldcr", format_4, ps_any }, { "stcr", format_4, ps_any }, + { "sbo", format_2b, ps_any }, { "sbz", format_2b, ps_any }, + { "tb", format_2b, ps_any }, { "jeq", format_2a, ps_any }, + { "jgt", format_2a, ps_any }, { "jh", format_2a, ps_any }, + { "jhe", format_2a, ps_any }, { "jl", format_2a, ps_any }, + { "jle", format_2a, ps_any }, { "jlt", format_2a, ps_any }, + { "jmp", format_2a, ps_any }, { "jnc", format_2a, ps_any }, + { "jne", format_2a, ps_any }, { "jno", format_2a, ps_any }, + { "joc", format_2a, ps_any }, { "jop", format_2a, ps_any }, + { "sla", format_5, ps_any }, { "sra", format_5, ps_any }, + { "src", format_5, ps_any }, { "srl", format_5, ps_any }, + { "ai", format_8a, ps_any|sd_11 }, { "andi", format_8a, ps_any|sd_11 }, + { "ci", format_8a, ps_any|sd_11 }, { "li", format_8a, ps_any|sd_11 }, + { "ori", format_8a, ps_any|sd_11 }, { "lwpi", format_8b, ps_any|sd_11|sd_11_15 }, + { "limi", format_8b, ps_any|sd_11|sd_11_15 }, { "stst", format_18, ps_any|sd_11 }, + { "stwp", format_18, ps_any|sd_11 }, { "rtwp", format_7, ps_any|sd_11|sd_11_15 }, + { "idle", format_7, ps_any|sd_11|sd_11_15 }, { "rset", format_7, ps_any|sd_11|sd_11_15 }, + { "ckof", format_7, ps_any|sd_11|sd_11_15 }, { "ckon", format_7, ps_any|sd_11|sd_11_15 }, + { "lrex", format_7, ps_any|sd_11|sd_11_15 }, + + /* mapper instruction set */ + { "lds", format_6, ps_mapper }, { "ldd", format_6, ps_mapper }, + { "lmf", format_10, ps_mapper }, + + /* tms9995 instruction set */ + { "divs", format_6, ps_tms9995 }, { "mpys", format_6, ps_tms9995 }, + { "lst", format_18, ps_tms9995 }, { "lwp", format_18, ps_tms9995 }, + + /* tms99000 instruction set */ + { "bind", format_6, ps_tms99000 }, + + /* ti990/12 instruction set */ + { "sram", format_13, ps_ti990_12 }, { "slam", format_13, ps_ti990_12 }, + { "rto", format_11, ps_ti990_12 }, { "lto", format_11, ps_ti990_12 }, + { "cnto", format_11, ps_ti990_12 }, { "slsl", format_20, ps_ti990_12 }, + { "slsp", format_20, ps_ti990_12 }, { "bdc", format_11, ps_ti990_12 }, + { "dbc", format_11, ps_ti990_12 }, { "swpm", format_11, ps_ti990_12 }, + { "xorm", format_11, ps_ti990_12 }, { "orm", format_11, ps_ti990_12 }, + { "andm", format_11, ps_ti990_12 }, { "sm", format_11, ps_ti990_12 }, + { "am", format_11, ps_ti990_12 }, { "mova", format_19, ps_ti990_12 }, + { "emd", format_7, ps_ti990_12 }, { "eint", format_7, ps_ti990_12 }, + { "dint", format_7, ps_ti990_12 }, { "stpc", format_18, ps_ti990_12 }, + { "cs", format_12, ps_ti990_12 }, { "seqb", format_12, ps_ti990_12 }, + { "movs", format_12, ps_ti990_12 }, { "lim", format_18, ps_ti990_12 }, + { "lcs", format_18, ps_ti990_12 }, { "blsk", format_8a, ps_ti990_12 }, + { "mvsr", format_12, ps_ti990_12 }, { "mvsk", format_12, ps_ti990_12 }, + { "pops", format_12, ps_ti990_12 }, { "pshs", format_12, ps_ti990_12 }, + { "cri", format_7, ps_ti990_12 }, { "cdi", format_7, ps_ti990_12 }, + { "negr", format_7, ps_ti990_12 }, { "negd", format_7, ps_ti990_12 }, + { "cre", format_7, ps_ti990_12 }, { "cde", format_7, ps_ti990_12 }, + { "cer", format_7, ps_ti990_12 }, { "ced", format_7, ps_ti990_12 }, + { "nrm", format_11, ps_ti990_12 }, { "tmb", format_14, ps_ti990_12 }, + { "tcmb", format_14, ps_ti990_12 }, { "tsmb", format_14, ps_ti990_12 }, + { "srj", format_17, ps_ti990_12 }, { "arj", format_17, ps_ti990_12 }, + { "xit", format_7, ps_ti990_12 }, { "insf", format_16, ps_ti990_12 }, + { "xv", format_16, ps_ti990_12 }, { "xf", format_16, ps_ti990_12 }, + { "ar", format_6, ps_ti990_12 }, { "cir", format_6, ps_ti990_12 }, + { "sr", format_6, ps_ti990_12 }, { "mr", format_6, ps_ti990_12 }, + { "dr", format_6, ps_ti990_12 }, { "lr", format_6, ps_ti990_12 }, + { "str", format_6, ps_ti990_12 }, { "iof", format_15, ps_ti990_12 }, + { "sneb", format_12, ps_ti990_12 }, { "crc", format_12, ps_ti990_12 }, + { "ts", format_12, ps_ti990_12 }, { "ad", format_6, ps_ti990_12 }, + { "cid", format_6, ps_ti990_12 }, { "sd", format_6, ps_ti990_12 }, + { "md", format_6, ps_ti990_12 }, { "dd", format_6, ps_ti990_12 }, + { "ld", format_6, ps_ti990_12 }, { "std", format_6, ps_ti990_12 }, + { "ep", format_21, ps_ti990_12 }, + + /* tms9940-only instruction set */ + /* these instructions are said to be format 9 (xop), but since the xop + level is interpreted as part of the opcode, dca and dcs should be handled + like format 6. liim looks like format 18, but slightly different, + therefore it is handled like a special format. */ + { "liim", format_liim,/*ps_tms9940*/0 }, { "dca", format_6, /*ps_tms9940*/0 }, + { "dcs", format_6, /*ps_tms9940*/0 }, + + { NULL, illegal, ps_any } +}; + + +static const enum opcodes ops_4000_ffff_s12[12]= +{ + _szc, _szcb, _s, _sb, /*4000-7000*/ + _c, _cb, _a, _ab, _mov, _movb, _soc, _socb /*8000-f000*/ +}; + + +static const enum opcodes ops_2000_3fff_s10[8]= +{ + _coc, _czc, _xor, _xop, _ldcr, _stcr, _mpy, _div /*2000-3800*/ +}; + + +static const enum opcodes ops_1000_1fff_s8[16]= +{ + _jmp, _jlt, _jle, _jeq, _jhe, _jgt, _jne, _jnc, /*1000-1700*/ + _joc, _jno, _jl, _jh, _jop, _sbo, _sbz, _tb /*1800-1f00*/ +}; + + +static const enum opcodes ops_0e40_0fff_s6[7]= +{ + _ad, _cid, _sd, _md, _dd, _ld, _std /*0e40-0fc0*/ +}; + + +static const enum opcodes ops_0e00_0e3f_s4[4]= +{ + _iof, _sneb, _crc, _ts /*0e00-0e30*/ +}; + + +static const enum opcodes ops_0c40_0dff_s6[7]= +{ + _ar, _cir, _sr, _mr, _dr, _lr, _str /*0c40-0dc0*/ +}; + + +static const enum opcodes ops_0c10_0c3f_s4[3]= +{ + _insf, _xv, _xf /*0c10-0c30*/ +}; + + +static const enum opcodes ops_0c00_0c0f_s0[16]= +{ + _cri, _cdi, _negr, _negd, _cre, _cde, _cer, _ced, /*0c00-0c07*/ + _nrm, _tmb, _tcmb, _tsmb, _srj, _arj, _xit, _xit /*0c08-0c0f*/ +}; + + + +static const enum opcodes ops_0800_0bff_s8[4]= +{ + _sra, _srl, _sla, _src /*0800-0b00*/ +}; + + +static const enum opcodes ops_0400_07ff_s6[16]= +{ + _blwp, _b, _x, _clr, _neg, _inv, _inc, _inct, /*0400-05c0*/ + _dec, _dect, _bl, _swpb, _seto, _abs, _lds, _ldd /*0600-07c0*/ +}; + + +static const enum opcodes ops_0200_03ff_s5[16]= +{ + _li, _ai, _andi, _ori, _ci, _stwp, _stst, _lwpi, /*0200-02e0*/ + _limi, _lmf, _idle, _rset, _rtwp, _ckon, _ckof, _lrex /*0300-03e0*/ +}; + + +static const enum opcodes ops_0100_01ff_s6[4]= +{ + _ill, _bind, _divs, _mpys /*0100-01c0*/ +}; + + +static const enum opcodes ops_0030_00ff_s4[13]= +{ + _stpc, _cs, _seqb, _movs, _lim, /*0030-0070*/ + _lst, _lwp, _lcs, _blsk, _mvsr, _mvsk, _pops, _pshs /*0080-00f0*/ +}; + + +static const enum opcodes ops_001c_002f_s0[20]= +{ + _sram, _slam, _rto, _lto, /*001c-001f*/ + _cnto, _slsl, _slsp, _bdc, _dbc, _swpm, _xorm, _orm, /*0020-0027*/ + _andm, _sm, _am, _mova, _ill, _emd, _eint, _dint /*0028-002f*/ +}; + + + +static int PC; + + +INLINE UINT16 readop_arg(const UINT8 *opram, unsigned pc) +{ + UINT16 result = opram[PC++ - pc] << 8; + return result | opram[PC++ - pc]; +} + +static int print_arg (char *dest, int mode, int arg, const UINT8 *opram, unsigned pc) +{ + int base; + + switch (mode) + { + case 0x0: /* workspace register */ + return sprintf (dest, "R%d", arg); + case 0x1: /* workspace register indirect */ + return sprintf (dest, "*R%d", arg); + case 0x2: /* symbolic|indexed */ + base = readop_arg(opram, pc); + if (arg) /* indexed */ + return sprintf (dest, "@>%04x(R%d)", base, arg); + else /* symbolic (direct) */ + return sprintf (dest, "@>%04x", base); + case 0x3: /* workspace register indirect auto increment */ + return sprintf (dest, "*R%d+", arg); + } + + return 0; +} + + +/***************************************************************************** + * Disassemble a single command and return the number of bytes it uses. + *****************************************************************************/ +unsigned Dasm9900 (char *buffer, unsigned pc, int model_id, const UINT8 *oprom, const UINT8 *opram) +{ + int OP, OP2, opc; + int sarg, darg, smode, dmode; + signed char displacement; + int byte_count, checkpoint; + int bit_position, bit_width; + unsigned dasmflags = 0; + + const char *mnemonic; + format_t format; + int flags; + + /* + Under tms9900, opcodes >0400->07FF are incompletely decoded: bits 11 is ignored, and so are + bits 12-15 for instructions which do not require a register. On the other hand, ti990/10 + generates an illegal instruction error when bit 11 is set, but still ignores bits 12-15. + Additionally, ti990/12 and tms9995 will generate an illegal error when bits 12-15 are + non-zero. + */ + #define BETTER_0200_DECODING (model_id == TI990_10_ID) + #define COMPLETE_0200_DECODING (/*(model_id == TI990_12_ID) ||*/ (model_id >= TMS9995_ID)) + + int processor_mask = ps_any; + + if ((model_id == TI990_10_ID) /*|| (model_id == TI990_12_ID)*/ || (model_id >= TMS99000_ID)) + processor_mask |= ps_mapper; /* processors with memory mapper (ti990/10, ti990/12, + and tms99000 with mapper coprocessor) */ + if (/*(model_id == TI990_12_ID) ||*/ (model_id >= TMS9995_ID)) + processor_mask |= ps_tms9995; /* ti990/12, tms9995, and later */ + + if (/*(model_id == TI990_12_ID) ||*/ (model_id >= TMS99000_ID)) + processor_mask |= ps_tms99000; /* ti990/12, tms99000, and later */ + + /*if ((model_id == TI990_12_ID)) + processor_mask |= ps_ti990_12;*/ /* ti990/12, tms99000, and later */ + + PC = pc; + OP = oprom[PC++ - pc] << 8; + OP |= oprom[PC++ - pc]; + + /* let's identify the opcode */ + if (OP >= 0x4000) + opc = ops_4000_ffff_s12[(OP - 0x4000) >> 12]; + else if (OP >= 0x2000) + opc = ops_2000_3fff_s10[(OP - 0x2000) >> 10]; + else if (OP >= 0x1000) + opc = ops_1000_1fff_s8[(OP - 0x1000) >> 8]; + else if (OP >= 0x0C00) + { + if (OP >= 0x0E40) + opc = ops_0e40_0fff_s6[(OP - 0x0E40) >> 6]; + else if (OP >= 0x0E00) + opc = ops_0e00_0e3f_s4[(OP - 0x0E00) >> 4]; + else if (OP >= 0x0C40) + opc = ops_0c40_0dff_s6[(OP - 0x0C40) >> 6]; + else if (OP >= 0x0C10) + opc = ops_0c10_0c3f_s4[(OP - 0x0C10) >> 4]; + else + opc = ops_0c00_0c0f_s0[OP - 0x0C00]; + } + else if (OP >= 0x0800) + opc = ops_0800_0bff_s8[(OP - 0x0800) >> 8]; + else if (OP >= 0x0400) + opc = ops_0400_07ff_s6[(OP - 0x0400) >> 6]; + else if (OP >= 0x0200) + { + opc = ops_0200_03ff_s5[(OP - 0x0200) >> 5]; + if (BETTER_0200_DECODING || COMPLETE_0200_DECODING) + { + flags = descriptions[opc].flags; + if ( (COMPLETE_0200_DECODING && (flags & sd_11_15) && (OP & 0x001f)) + || ((flags & sd_11) && (OP & 0x0010)) ) + { + opc = _ill; + if (OP >= 0x03f0) + opc = _ep; /* the ep opcode is located in a "hole" */ + } + } + } + else if (OP >= 0x0100) + opc = ops_0100_01ff_s6[(OP - 0x0100) >> 6]; + else if (OP >= 0x0030) + opc = ops_0030_00ff_s4[(OP - 0x0030) >> 4]; + else if (OP >= 0x001C) + opc = ops_001c_002f_s0[OP - 0x001C]; + else + opc = _ill; + + /* read flags */ + flags = descriptions[opc].flags; + /* set as illegal if the processor does not implement this instruction */ + if (! (flags & processor_mask)) + { + opc = _ill; + flags = descriptions[opc].flags; /* read new flags */ + } + + /* tms9940 replace a few xops with custom instructions */ + if ((opc == _xop) && ((model_id == TMS9940_ID) || (model_id == TMS9985_ID))) + { + switch (BITS(OP,6,9)) + { + case 0: + /* opcode is dca */ + opc = _dca; + break; + + case 1: + /* opcode is dcs */ + opc = _dcs; + break; + + case 2: + case 3: /* should be 2, but instruction decoding is incomplete */ + /* opcode is liim */ + if (BITS(OP,12,15) == 0) + /* ts must be == 0 */ + opc = _liim; + else + /* I don't know what happens when ts != 0. Maybe the CPU does + the complete address decoding, and liim gets a bogus value + instead of the immediate. Since I do not know, I handle this + as an illegal instruction. */ + opc = _ill; + break; + + default: + /* this is still a software xop */ + break; + } + } + + mnemonic = descriptions[opc].mnemonic; + format = descriptions[opc].format; + + /* bl and blwp instructions are subroutines */ + if (mnemonic != NULL && mnemonic[0] == 'b' && mnemonic[1] == 'l') + dasmflags = DASMFLAG_STEP_OVER; + + /* b *r11 and rtwp are returns */ + else if (opc == 0x045b || (mnemonic != NULL && strcmp(mnemonic, "rtwp") == 0)) + dasmflags = DASMFLAG_STEP_OUT; + + switch (format) + { + case format_1: /* 2 address instructions */ + smode = BITS(OP,10,11); + sarg = BITS(OP,12,15); + dmode = BITS(OP,4,5); + darg = BITS(OP,6,9); + + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, ","); + buffer += print_arg (buffer, dmode, darg, opram, pc); + break; + + case format_2a: /* jump instructions */ + displacement = (signed char)BITS(OP,8,15); + sprintf (buffer, "%-4s >%04x", mnemonic, 0xffff & (PC + displacement * 2)); + break; + + case format_2b: /* bit I/O instructions */ + displacement = (signed char)BITS(OP,8,15); + sprintf (buffer, "%-4s >%04x", mnemonic, 0xffff & displacement); + break; + + case format_3_9: /* logical, multiply, and divide instructions */ + case format_4: /* CRU instructions */ + case format_9: /* extended operation instruction */ + smode = BITS(OP,10,11); + sarg = BITS(OP,12,15); + darg = BITS(OP,6,9); + + if ((darg == 0) && (format == format_4)) + darg = 16; + + if (format == format_3_9) + { + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, ",R%d", darg); + } + else + { + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, ",%d", darg); + } + break; + + case format_5: /* register shift instructions */ + sarg = BITS(OP,12,15); + darg = BITS(OP,8,11); + + sprintf (buffer, darg ? "%-4s R%d,%d" : "%-4s R%d,R%d", mnemonic, sarg, darg); + break; + + case format_6: /* single address instructions */ + smode = BITS(OP,10,11); + sarg = BITS(OP,12,15); + + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + break; + + case format_7: /* instructions without operands */ + sprintf (buffer, "%s", mnemonic); + break; + + case format_8a: /* immediate instructions (destination register) */ + darg = BITS(OP,12,15); + sarg = readop_arg(opram, pc); + + sprintf (buffer, "%-4s R%d,>%04x", mnemonic, darg, sarg); + break; + + case format_8b: /* immediate instructions (no destination register) */ + sarg = readop_arg(opram, pc); + + sprintf (buffer, "%-4s >%04x", mnemonic, sarg); + break; + + case format_10: /* memory map file instruction */ + sarg = BITS(OP,12,15); + darg = BITS(OP,11,11); + + sprintf (buffer, "%-4s R%d,%d", mnemonic, sarg, darg); + break; + + case format_11: /* multiple precision instructions */ + OP2 = readop_arg(opram, pc); + + smode = BITS(OP2,10,11); + sarg = BITS(OP2,12,15); + dmode = BITS(OP2,4,5); + darg = BITS(OP2,6,9); + byte_count = BITS(OP2,0,3); + + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, ","); + buffer += print_arg (buffer, dmode, darg, opram, pc); + buffer += sprintf (buffer, byte_count ? ",%d" : ",R%d", byte_count); + break; + + case format_12: /* string instructions */ + OP2 = readop_arg(opram, pc); + + smode = BITS(OP2,10,11); + sarg = BITS(OP2,12,15); + dmode = BITS(OP2,4,5); + darg = BITS(OP2,6,9); + byte_count = BITS(OP2,0,3); + checkpoint = BITS(OP,12,15); + + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, ","); + buffer += print_arg (buffer, dmode, darg, opram, pc); + buffer += sprintf (buffer, byte_count ? ",%d,R%d" : ",R%d,R%d", byte_count, checkpoint); + break; + + case format_13: /* multiple precision shift instructions */ + OP2 = readop_arg(opram, pc); + + smode = BITS(OP2,10,11); + sarg = BITS(OP2,12,15); + darg = BITS(OP2,6,9); + byte_count = BITS(OP2,0,3); + + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, byte_count ? ",%d" : ",R%d", byte_count); + buffer += sprintf (buffer, darg ? ",%d" : ",R%d", darg); + break; + + case format_14: /* bit testing instructions */ + OP2 = readop_arg(opram, pc); + + smode = BITS(OP2,10,11); + sarg = BITS(OP2,12,15); + darg = BITS(OP2,0,9); + + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + if (darg == 0x3ff) + buffer += sprintf (buffer, ",R0"); + else + buffer += sprintf (buffer, ",%d", darg); + break; + + case format_15: /* invert order of field instruction */ + OP2 = readop_arg(opram, pc); + + smode = BITS(OP2,10,11); + sarg = BITS(OP2,12,15); + bit_position = BITS(OP2,0,3); + bit_width = BITS(OP,12,15); + + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, bit_position ? ",(%d," : ",(R%d,", bit_position); + buffer += sprintf (buffer, bit_width ? "%d)" : "R%d)", bit_width); + break; + + case format_16: /* field instructions */ + OP2 = readop_arg(opram, pc); + + smode = BITS(OP2,10,11); + sarg = BITS(OP2,12,15); + dmode = BITS(OP2,4,5); + darg = BITS(OP2,6,9); + bit_position = BITS(OP2,0,3); + bit_width = BITS(OP,12,15); + + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, ","); + buffer += print_arg (buffer, dmode, darg, opram, pc); + buffer += sprintf (buffer, bit_position ? ",(%d," : ",(%d,", bit_position); + buffer += sprintf (buffer, bit_width ? "%d)" : "R%d)", bit_width); + break; + + case format_17: /* alter register and jump instructions */ + OP2 = readop_arg(opram, pc); + + displacement = (signed char)BITS(OP2,8,15); + sarg = BITS(OP2,4,7); + darg = BITS(OP2,0,3); + if (darg) + sprintf (buffer, darg ? "%-4s >%04x,%d,R%d" : "%-4s >%04x,R%d,R%d", + mnemonic, 0xffff & (PC + displacement * 2), sarg, darg); + break; + + case format_18: /* single register operand instructions */ + sarg = BITS(OP,12,15); + + sprintf (buffer, "%-4s R%d", mnemonic, sarg); + break; + + case format_liim: /* liim instruction */ + sarg = BITS(OP,14,15); + + sprintf (buffer, "%-4s %d", mnemonic, sarg); + break; + + case format_19: /* move address instruction */ + OP2 = readop_arg(opram, pc); + + smode = BITS(OP2,10,11); + sarg = BITS(OP2,12,15); + dmode = BITS(OP2,4,5); + darg = BITS(OP2,6,9); + + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, ","); + buffer += print_arg (buffer, dmode, darg, opram, pc); + break; + + case format_20: /* list search instructions */ + { + const char *condition_code; + + OP2 = readop_arg(opram, pc); + + smode = BITS(OP2,10,11); + sarg = BITS(OP2,12,15); + dmode = BITS(OP2,4,5); + darg = BITS(OP2,6,9); + + switch (BITS(OP2,0,3)) + { + case 0: + condition_code = "eq"; + break; + case 1: + condition_code = "ne"; + break; + case 2: + condition_code = "he"; + break; + case 3: + condition_code = "l"; + break; + case 4: + condition_code = "ge"; + break; + case 5: + condition_code = "lt"; + break; + case 6: + condition_code = "le"; + break; + case 7: + condition_code = "h"; + break; + case 8: + condition_code = "lte"; + break; + case 9: + condition_code = "gt"; + break; + default: + condition_code = "??"; + break; + } + + buffer += sprintf (buffer, "%-4s %s,", mnemonic, condition_code); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, ","); + buffer += print_arg (buffer, dmode, darg, opram, pc); + break; + } + + case format_21: /* extend precision instruction */ + { + int dest_byte_count; + + OP2 = readop_arg(opram, pc); + + smode = BITS(OP2,10,11); + sarg = BITS(OP2,12,15); + dmode = BITS(OP2,4,5); + darg = BITS(OP2,6,9); + byte_count = BITS(OP2,0,3); + dest_byte_count = BITS(OP,12,15); + + buffer += sprintf (buffer, "%-4s ", mnemonic); + buffer += print_arg (buffer, smode, sarg, opram, pc); + buffer += sprintf (buffer, ","); + buffer += print_arg (buffer, dmode, darg, opram, pc); + buffer += sprintf (buffer, byte_count ? ",%d" : ",R%d", byte_count); + buffer += sprintf (buffer, dest_byte_count ? ",%d" : ",R%d", dest_byte_count); + break; + } + + default: + logerror("debbugger internal error, file %s, line %d\n", __FILE__, __LINE__); + case illegal: + sprintf (buffer, "data >%04x", OP); + break; + } + + return (PC - pc) | DASMFLAG_SUPPORTED | dasmflags; +} + +CPU_DISASSEMBLE( tms9900 ) +{ + return Dasm9900(buffer, pc, TMS9900_ID, oprom, opram); +} + +CPU_DISASSEMBLE( tms9980 ) +{ + return Dasm9900(buffer, pc, TMS9980_ID, oprom, opram); +} + +CPU_DISASSEMBLE( tms9995 ) +{ + return Dasm9900(buffer, pc, TMS9995_ID, oprom, opram); +} diff --git a/src/devices/cpu/tms9900/99xxcore.h b/src/devices/cpu/tms9900/99xxcore.h new file mode 100644 index 00000000000..cb94fac286f --- /dev/null +++ b/src/devices/cpu/tms9900/99xxcore.h @@ -0,0 +1,5319 @@ +// license:BSD-3-Clause +// copyright-holders:Raphael Nabet + +/*************************************************************************** + Legacy TMS99xx core implementation + + PLEASE DO NOT REMOVE THIS FILE from the source tree, even if none of the + drivers make use of it anymore. It still contains documentation and + incomplete implementations of cores that are not (yet) used in drivers. + + It need not be included in the build process, however. + + Michael Zapf, February 2014 + +***************************************************************************/ + +/* + 99xxcore.h : generic tms99xx emulation + + The TMS99XX_MODEL switch tells which emulator we want to build. Set the + switch, then include 99xxcore.h, and you will have an emulator for this + processor. + + Only ti990/10, tms9900, tms9980a/9981, and tms9995 work OK for now. Note + that tms9995 has not been tested extensively. + + tms9940 is WIP: it is probably still buggy (particularily the BCD support), + as it has not been tested. tms9985 has been implemented as a 9940 with + a data bus, which should be mostly correct. + + I think all software aspects of tms9985 and tms9989 are implemented. + You just need to implement bus interfaces, provided you know them. + (I don't...) + + tms99000 cannot be implemented fully yet, due to lack of documentation. + + ti990/12 is not supported at all, and it should probably be implemented as + a separate processor core, anyway. + + Original tms9900 emulator by Edward Swartz + Smoothed out by Raphael Nabet + Originally converted for Mame by M.Coates + Processor timing, support for tms9980 and tms9995, and many bug fixes by R Nabet +*/ + +/* + The first member of the family was actually the ti990/10 minicomputer, + released in 1975. tms9900 was released in 1976, and has the same + instruction set as ti990/10: however, tms9900 is slower, it does not + support privileges and memory mapping, and illegal instructions do not + cause an error interrupt. + + The ti990 family later evoluted into the huge ti990/12 system, with support + for 144 different instructions, and microcode programming in case some user + found it was not enough. ti990/10 was eventually replaced by a cheaper + ti990/10a board, built around a tms99000 microprocessor. + + The tms9980 processor is merely a tms9900 with a 8-bit data bus (instead of + 16-bit on tms9900). + + tms9940 is a microcontroller, and is mostly similar to 9900/9980. The + variant I know has 2kb of ROM, 128 bytes of RAM, a timer, 32 I/O line, some + of which can be reconfigured as a CRU bus, but no external memory bus. It + includes three additional opcodes, which are not supported by any other + member of the family (with the probable exception of TMS9985). + + tms9985 is similar to tms9940, but it supports an external 8-bit-wide + memory bus. At least one variant included 8kb of ROM, 256 bytes of RAM. + It was ill-fated, as it was never released due to technical problems. + + tms9989 is mostly alien to me. I guess it is a close relative of tms9995, + although I am not sure. I have read that the SBP68689 supports tms9995 + opcodes, but that tms9989 does not. + + tms9995 belongs to another generation. It is quite faster than tms9900, + and supports 4 extra opcodes. Its external bus is 8-bit-wide, and it has + 256 bytes of internal 16-bit RAM. + + tms99000 is the successor to both ti9900 and ti990/10. It supports + privileges, and has a coprocessor interface which enables the use of an + external memory mapper. Additionnally, it can use a Macrostore ROM to + emulate additional instructions. + + This feature allowed TI to expand the 99000 family with the tms99105 (which + was said to support 84 instructions types), the tms99110 (which supported + floating point instructions), and possibly another chip (tms99220???) which + included parts of the UCSD P-system in Macrostore. + +References : +* 9900 family systems design, chapter 6, 7, 8 +* TMS 9980A/ TMS 9981 Product Data Book +* TMS 9995 16-Bit Microcomputer Data Manual +* Model 990/10A computer general description, section 4 (p/n 2302633-9701 A) +* 990/99000 assembly language reference manual (p/n 2270509-9701 A) +* Chapter 18 of unidentified book is the only reference on tms9940 I have found so far (Paperport format) + <ftp://ftp.whtech.com//datasheets/Hardware manuals/tms9900 9901 9904 9940 9980 (by a osborne).max> + +Other references can be found on spies.com: +<http://www.spies.com/arcade/simulation/processors/index.html> +<http://www.spies.com/~aek/pdf/ti/> + +*/ + + +#if (TMS99XX_MODEL == TI990_10_ID) + + #define TMS99XX_PREFIX ti990_10 + #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( ti990_10l ) + #define TMS99XX_device_get_name "TI990/10L" + #define TMS99XX_device_get_shortname "ti990_10l" + +#elif (TMS99XX_MODEL == TMS9900_ID) + + #define TMS99XX_PREFIX tms9900 + #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9900l ) + #define TMS99XX_device_get_name "TMS9900L" + #define TMS99XX_device_get_shortname "tms9900l" + +#elif (TMS99XX_MODEL == TMS9940_ID) + + #define TMS99XX_PREFIX tms9940 + #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9940l ) + #define TMS99XX_device_get_name "TMS9940L" + #define TMS99XX_device_get_shortname "tms9940l" + + #error "tms9940 is not yet supported" + +#elif (TMS99XX_MODEL == TMS9980_ID) + + #define TMS99XX_PREFIX tms9980a + #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9980al ) + #define TMS99XX_device_get_name "TMS9980AL/TMS9981L" + #define TMS99XX_device_get_shortname "tms9980al" + +#elif (TMS99XX_MODEL == TMS9985_ID) + + #define TMS99XX_PREFIX tms9985 + #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9985l ) + #define TMS99XX_device_get_name "TMS9985L" + #define TMS99XX_device_get_shortname "tms9985l" + + #error "tms9985 is not yet supported" + +#elif (TMS99XX_MODEL == TMS9989_ID) + + #define TMS99XX_PREFIX tms9989 + #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9989l ) + #define TMS99XX_device_get_name "TMS9989L" + #define TMS99XX_device_get_shortname "tms9989l" + + #error "tms9989 is not yet supported" + +#elif (TMS99XX_MODEL == TMS9995_ID) + + #define TMS99XX_PREFIX tms9995 + #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9995l ) + #define TMS99XX_device_get_name "TMS9995L" + #define TMS99XX_device_get_shortname "tms9995l" + +#elif (TMS99XX_MODEL == TMS99000_ID) + + #define TMS99XX_PREFIX tms99000 + #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms99000l ) + #define TMS99XX_device_get_name "TMS99000L" + #define TMS99XX_device_get_shortname "tms99000l" + + #error "tms99000 is not yet supported" + +#elif (TMS99XX_MODEL == TMS99105A_ID) + + #define TMS99XX_PREFIX tms99105a + #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms99105al ) + #define TMS99XX_device_get_name "TMS99105AL" + #define TMS99XX_device_get_shortname "tms99105al" + + #error "tms99105a is not yet supported" + +#elif (TMS99XX_MODEL == TMS99110A_ID) + + #define TMS99XX_PREFIX tms99110a + #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms99110al ) + #define TMS99XX_device_get_name "TMS99110AL" + #define TMS99XX_device_get_shortname "tms99110al" + + #error "tms99110a is not yet supported" + +#endif + +/* + Now for some preprocessor wizardry. +*/ +#define concat2(a,b) a##b + +#define GET_INFO(prefix) concat2(prefix,_get_info) +#define RESET_PARAM(prefix) concat2(prefix,reset_param) + +#define TMS99XX_RESET_PARAM RESET_PARAM(TMS99XX_PREFIX) + + + +/* + I include this macro because we may eventually support other 99000 variants such as tms99110, + and this macro will remain true for every 99000 family member, even when we have + (TMS99XX_MODEL != TMS99000_ID). +*/ +#define IS_99000 (TMS99XX_MODEL == TMS99000_ID) + +/* + On microprocessor implementations (other than TMS9940 and, probably, TMS9985), the CKOF, CKON, + IDLE, LREX and RSET cause an external CRU write. CKOF, CKON and LREX do nothing apart of this, + therefore they must be implemented with external hardware (CKON and CKOF are supposed to + enable/disable a line clock interrupt, and LREX to trigger a LOAD interrupt). IDLE and RSET + are functional, but, on the one hand, the design allowed to light a diagnostic LED when + the processor is in IDLE state, and, on the other hand, the RSET instruction is supposed + to reset external devices as well. + + On the TI990/10 and TI990/12 minicomputers, there is no such CRU write. The line clock + interrupt latch is part of the CPU board, LREX is fully functional, the IDLE led is connected + to the board, and the RSET line is part of the TILINE bus connector. + + On the TMS9940, CKOF, CKON, LREX and RSET are not supported. IDLE, on the other hand, is + supported, and the CPU can be configured to output its IDLE state on the P16 I/O pin. +*/ +#define EXTERNAL_INSTRUCTION_DECODING (TMS99XX_MODEL != TI990_10_ID) && (TMS99XX_MODEL != TI9940_ID) && (TMS99XX_MODEL != TI9985_ID) +#define EXTERNAL_INSTRUCTION_CALLBACK (TMS99XX_MODEL == TI990_10_ID) + +/* + ti990/10, ti990/12 and tms99000 support privileges + + privileged instructions: + CKOF, CKON, IDLE, LIMI, LREX, RSET, + LDD, LDS, LMF, (memory mapping instructions) + DINT, EINT, EMD , LCS, LIM, SLSP. (990/12 instructions) + + instructions which are privileged when the effective CRU address is higher than 0xE00: + LDCR, SBO, SBZ, STCR, TB. + + instructions whose behaviour is modified in user mode (only user flags in ST are affected): + RTWP, + LST. (ti990/12 and tms99000 instruction) +*/ +#define HAS_PRIVILEGE ((TMS99XX_MODEL == TI990_10_ID) || IS_99000) + +/* + opcode groups + + * 69 basic opcodes implemented on all family members + * 3 memory mapping opcodes implemented on ti990/10 with mapping option, ti990/12, and + the tim99610 mapper in conjunction with any tms99000 family member: LMF, LDS, LDD + * 3 opcodes implemented on tms9940 (and probably tms9985) only: DCA, DCS, LIIM + * 4 opcodes implemented on ti990/12, tms9989 and above: MPYS, DIVS, LST, LWP + * 1 opcode implemented on ti990/12, and tms99000: BIND + * 72 opcodes implemented on ti990/12 only (some of which are emulated by tms99105 & tms99110) +*/ + +#define HAS_MAPPING ((TMS99XX_MODEL == TI990_10_ID) /*|| IS_99000*/) +#define HAS_9995_OPCODES ((TMS99XX_MODEL == TMS9989_ID) || (TMS99XX_MODEL == TMS9995_ID) || IS_99000) +#define HAS_BIND_OPCODE IS_99000 + +#define HAS_OVERFLOW_INTERRUPT ((TMS99XX_MODEL == TMS9995_ID) || IS_99000) + +/* + Under tms9900, opcodes >0200->03FF are incompletely decoded: bits 11 is ignored, and so are + bits 12-15 for instructions which do not require a register. On the other hand, ti990/10 + generates an illegal instruction error when bit 11 is set, but still ignores bits 12-15. + Additionally, ti990/12 and tms9995 will generate an illegal error when bits 12-15 are + non-zero. +*/ +#define BETTER_0200_DECODING (TMS99XX_MODEL == TI990_10_ID) +#define COMPLETE_0200_DECODING (TMS99XX_MODEL >= TMS9995_ID) + +/* + TI990/10 and tms9900 force unused bits in the ST register to 0. + TMS9995 does not. +*/ +#define USE_ST_MASK (TMS99XX_MODEL <= TMS9985_ID) + +/* + TI990/10, TMS9900 and TMS99000 have a 16-bit-wide memory bus, and use 16-bus accessors. + TMS9940 use 16-bit accessors, too, as it has an internal 16-bit bus, and no external bus. +*/ +#define USE_16_BIT_ACCESSORS ((TMS99XX_MODEL == TI990_10_ID) || (TMS99XX_MODEL == TMS9900_ID) || (TMS99XX_MODEL == TMS9940_ID) || (IS_99000)) + +struct tms99xx_state; + +INLINE void execute(tms99xx_state *cpustate, UINT16 opcode); + +#if EXTERNAL_INSTRUCTION_DECODING +static void external_instruction_notify(tms99xx_state *cpustate, int ext_op_ID); +#endif +static UINT16 decipheraddr(tms99xx_state *cpustate, UINT16 opcode); +static UINT16 decipheraddrbyte(tms99xx_state *cpustate, UINT16 opcode); +static void contextswitch(tms99xx_state *cpustate, UINT16 addr); +#if HAS_MAPPING || HAS_PRIVILEGE +static void contextswitchX(tms99xx_state *cpustate, UINT16 addr); +#else +#define contextswitchX(cs, addr) contextswitch(cs, addr) +#endif +static void field_interrupt(tms99xx_state *cpustate); + +/***************************/ +/* Mame Interface Routines */ +/***************************/ + + +/* tms9900 ST register bits. */ + +/* These bits are set by every compare, move and arithmetic or logical operation : */ +/* (Well, COC, CZC and TB only set the E bit, but these are kind of exceptions.) */ +#define ST_LGT 0x8000 /* Logical Greater Than (strictly) */ +#define ST_AGT 0x4000 /* Arithmetical Greater Than (strictly) */ +#define ST_EQ 0x2000 /* Equal */ + +/* These bits are set by arithmetic operations, when it makes sense to update them. */ +#define ST_C 0x1000 /* Carry */ +#define ST_OV 0x0800 /* OVerflow (overflow with operations on signed integers, */ + /* and when the result of a 32bits:16bits division cannot fit in a 16-bit word.) */ + +/* This bit is set by move and arithmetic operations WHEN THEY USE BYTE OPERANDS. */ +#define ST_OP 0x0400 /* Odd Parity */ + +#if (TMS99XX_MODEL != TMS9940_ID) + +/* This bit is set by the XOP instruction. */ +#define ST_X 0x0200 /* Xop */ + +#endif + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + +/* This bit is set by arithmetic operations to support BCD */ +#define ST_DC 0x0100 /* Digit Carry */ + +#endif + +#if HAS_PRIVILEGE + +/* This bit is set in user (i.e. non-supervisor) mode */ +#define ST_PR 0x0100 /* PRivilege */ + +#endif + +#if HAS_MAPPING + +/* This tells which map is currently in use */ +#define ST_MF 0x0080 /* Map File */ + +#endif + +#if (HAS_OVERFLOW_INTERRUPT) + +/* This bit is set in ti990/12, TMS9995 and later chips to generate a level-2 interrupt when +the Overflow status bit is set */ +#define ST_OVIE 0x0020 /* OVerflow Interrupt Enable */ + +#endif + +#if (IS_99000) + +/* This bit enables the macrostore feature on tms99000 */ +/* (It is used on ti990/12 with a different meaning.) */ +#define ST_EMR 0x0010 /* Enable Macrostore Rom */ + +#endif + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + +#define ST_IM 0x0003 /* Interrupt Mask */ + +#else + +#define ST_IM 0x000F /* Interrupt Mask */ + +#endif + +/* On models before TMS9995 (TMS9989 ?), unused ST bits are always forced to 0, so we define +a ST_MASK */ +#if (USE_ST_MASK) + #if (TMS99XX_MODEL == TI990_10_ID) + #define ST_MASK 0xFF8F + #elif (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) +#define ST_MASK 0xFD03 + #else + #define ST_MASK 0xFE0F + #endif +#endif + + +/* error interrupt register bits */ + +#if (TMS99XX_MODEL == TI990_10_ID) + +/* This bit is set in user (i.e. non-supervisor) mode */ +#define EIR_MAPERR 0x0800 /* memory MAPping ERRor */ +#define EIR_MER 0x1000 /* Memory parity ERRor */ +#define EIR_ILLOP 0x2000 /* ILLegal OPcode */ +#define EIR_PRIVOP 0x4000 /* PRiviledged OPeration */ +#define EIR_TIMEOUT 0x8000 /* TILINE TIMEOUT */ + +#endif + + +/* Offsets for registers. */ +#define R0 0 +#define R1 2 +#define R2 4 +#define R3 6 +#define R4 8 +#define R5 10 +#define R6 12 +#define R7 14 +#define R8 16 +#define R9 18 +#define R10 20 +#define R11 22 +#define R12 24 +#define R13 26 +#define R14 28 +#define R15 30 + +struct map_file_t +{ + UINT16 L[3], B[3]; /* actual registers */ + UINT32 limit[3], bias[3]; /* equivalent in a more convenient form */ +}; + +struct tms99xx_state +{ +/* "actual" tms9900 registers : */ + UINT16 WP; /* Workspace pointer */ + UINT16 PC; /* Program counter */ + UINT16 STATUS; /* STatus register */ + +/* Now, data used for emulation */ + UINT8 lastparity; + char lds_flag, ldd_flag; + UINT16 IR; /* Instruction register, with the currently parsed opcode */ + + UINT8 interrupt_pending; /* true if an interrupt must be honored... */ + +#if ! ((TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID)) + UINT8 load_state; /* nonzero if the LOAD* line is active (low) */ +#endif + +#if (TMS99XX_MODEL == TI990_10_ID) || (TMS99XX_MODEL == TMS9900_ID) || (TMS99XX_MODEL == TMS9980_ID) + /* On tms9900, we cache the state of INTREQ* and IC0-IC3 here */ + /* On tms9980/9981, we translate the state of IC0-IC2 to the equivalent state for a tms9900, + and store the result here */ + UINT8 irq_level; /* when INTREQ* is active, interrupt level on IC0-IC3 ; else always 16 */ + UINT8 irq_state; /* nonzero if the INTREQ* line is active (low) */ + /* with TMS9940, bit 0 means INT1, bit 1 decrementer, bit 2 INT2 */ +#elif (TMS99XX_MODEL == TMS9995_ID) + /* tms9995 is quite different : it latches the interrupt inputs */ + UINT8 irq_level; /* We store the level of the request with the highest level here */ + UINT8 int_state; /* interrupt lines state */ + UINT8 int_latch; /* interrupt latches state */ +#endif + + /* interrupt callback */ + /* note that this callback is used by tms9900_set_irq_line(cpustate) and tms9980a_set_irq_line(cpustate) to + retreive the value on IC0-IC3 (non-standard behaviour) */ + device_irq_acknowledge_delegate irq_callback; + legacy_cpu_device *device; + address_space *program; + address_space *io; + int icount; + + UINT8 IDLE; /* nonzero if processor is IDLE - i.e waiting for interrupt while writing + special data on CRU bus */ + +#if HAS_MAPPING + UINT8 mapping_on; /* set by a CRU write */ + map_file_t map_files[3]; /* internal mapper registers */ + UINT8 cur_map; /* equivalent to ST_MF status bit */ + UINT8 cur_src_map; /* set to 2 by LDS */ + UINT8 cur_dst_map; /* set to 2 by LDD */ + +#if (TMS99XX_MODEL == TI990_10_ID) + UINT8 reset_maperr; /* reset mapper error flag line (reset flags in 945417-9701 p. 3-90) */ + + UINT32 mapper_address_latch; /* used to load the map file and for diagnostic purpose */ + UINT16 mapper_cru_read_register; /* read register select code for mapper cru interface */ + UINT8 diaglat; /* set when diagnostic address latch is done */ + UINT8 latch_control[3]; /* latch control */ + +#endif +#endif + +#if (TMS99XX_MODEL == TI990_10_ID) + UINT16 error_interrupt_register; /* one flag for each interrupt condition */ + ti99xx_error_interrupt_func error_interrupt_callback; +#endif + +#if (TMS99XX_MODEL == TMS9985_ID) || (TMS99XX_MODEL == TMS9995_ID) + UINT8 RAM[256]; /* on-chip RAM (I know this is weird, but the internal bus is 16-bit-wide, whereas the external bus is 8-bit-wide) */ +#endif + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) || (TMS99XX_MODEL == TMS9995_ID) + /* on-chip event counter/timer*/ + UINT8 decrementer_enabled; + UINT16 decrementer_interval; + UINT16 decrementer_count; /* used in event counter mode*/ + emu_timer *timer; /* used in timer mode */ +#endif + +#if (TMS99XX_MODEL == TMS9995_ID) + /* additionnal registers */ + UINT16 flag; /* flag register */ + UINT8 MID_flag; /* MID flag register */ + + /* chip config, which can be set on reset */ + UINT8 memory_wait_states_byte; + UINT8 memory_wait_states_word; + + /* mask option (off on normal tms9995) */ + UINT8 is_mp9537; +#endif + + /* Some instructions (i.e. XOP, BLWP, and MID) disable interrupt recognition until another + instruction is executed : so they set this flag */ + UINT8 disable_interrupt_recognition; + + /* notify the driver of changes in IDLE state */ + ti99xx_idle_func idle_callback; + +#if EXTERNAL_INSTRUCTION_CALLBACK + ti99xx_rset_func rset_callback; + ti99xx_lrex_func lrex_callback; + ti99xx_ckon_ckof_func ckon_ckof_callback; +#endif + + int write_inhibit; + int extra_byte; /* buffer holding the unused byte in a word read */ +}; + +INLINE tms99xx_state *get_safe_token(device_t *device) +{ + assert(device != NULL); +// assert(device->type() == TMS99XX_GET_INFO); + return (tms99xx_state *)downcast<legacy_cpu_device *>(device)->token(); +} + +#if (TMS99XX_MODEL == TMS9995_ID) +static void reset_decrementer(tms99xx_state *cpustate); +#endif + + +#if (TMS99XX_MODEL == TI990_10_ID) + +/* + accessor for the internal ROM +*/ +READ16_HANDLER(ti990_10_internal_r) +{ + //return cpustate->ROM[offset]; + return space.read_word(0x1ffc00+offset); +} + +#endif + +#if (TMS99XX_MODEL == TMS9995_ID) + +/* + accessor for the first 252 bytes of internal RAM +*/ +READ8_HANDLER(tms9995_internal1_r) +{ + tms99xx_state *cpustate = get_safe_token(&space.device()); + return cpustate->RAM[offset]; +} + +WRITE8_HANDLER(tms9995_internal1_w) +{ + tms99xx_state *cpustate = get_safe_token(&space.device()); + cpustate->RAM[offset]=data; +} + +/* + accessor for the last 4 bytes of internal RAM +*/ +READ8_HANDLER(tms9995_internal2_r) +{ + tms99xx_state *cpustate = get_safe_token(&space.device()); + return cpustate->RAM[offset+0xfc]; +} + +WRITE8_HANDLER(tms9995_internal2_w) +{ + tms99xx_state *cpustate = get_safe_token(&space.device()); + cpustate->RAM[offset+0xfc]=data; +} + +#endif + +#if (TMS99XX_MODEL == TI990_10_ID) + + /* on-board ROMs are not emulated (we use a hack) */ + + #define readword(cs, addr) readwordX((cs), (addr), (cs)->cur_map) + static int readwordX(tms99xx_state *cpustate, int addr, int map_file) + { + if ((map_file == 0) && (addr >= 0xf800)) + { /* intercept TPCS and CPU ROM */ + if (addr < 0xfc00) + /* TPCS */ + return cpustate->program->read_word(0x1f0000+addr); + else + /* CPU ROM */ + return cpustate->program->read_word(0x1f0000+addr); /* hack... */ + } + else if (! cpustate->mapping_on) + { + return cpustate->program->read_word(addr); + } + else + { + int map_index; + + if (addr <= cpustate->map_files[map_file].limit[0]) + map_index = 0; + else if (addr <= cpustate->map_files[map_file].limit[1]) + map_index = 1; + else if (addr <= cpustate->map_files[map_file].limit[2]) + map_index = 2; + else + { /* mapping error */ + if ((! (cpustate->error_interrupt_register & EIR_MAPERR)) && ! (cpustate->diaglat)) + cpustate->mapper_address_latch = addr; + if ((! cpustate->reset_maperr) && ! (cpustate->error_interrupt_register & EIR_MAPERR)) + { + cpustate->error_interrupt_register |= EIR_MAPERR; + cpustate->write_inhibit = 1; + } + return cpustate->program->read_word(addr); + } + if ((! (cpustate->error_interrupt_register & EIR_MAPERR)) && ! (cpustate->diaglat)) + cpustate->mapper_address_latch = cpustate->map_files[map_file].bias[map_index]+addr; + if ((cpustate->latch_control[map_index]) && (! cpustate->reset_maperr)) + cpustate->diaglat = 1; + return cpustate->program->read_word(cpustate->map_files[map_file].bias[map_index]+addr); + } + } + + #define writeword(cs, addr, data) writewordX((cs), (addr), (data), (cs)->cur_map) + static void writewordX(tms99xx_state *cpustate, int addr, int data, int map_file) + { + if ((map_file == 0) && (addr >= 0xf800)) + { /* intercept TPCS and CPU ROM */ + if (addr < 0xfc00) + /* TPCS */ + cpustate->program->write_word(0x1f0000+addr, data); + else + /* CPU ROM */ + cpustate->program->write_word(0x1f0000+addr, data); /* hack... */ + } + else if (! cpustate->mapping_on) + { + cpustate->program->write_word(addr, data); + } + else + { + int map_index; + + if (addr <= cpustate->map_files[map_file].limit[0]) + map_index = 0; + else if (addr <= cpustate->map_files[map_file].limit[1]) + map_index = 1; + else if (addr <= cpustate->map_files[map_file].limit[2]) + map_index = 2; + else + { /* mapping error */ + if ((! (cpustate->error_interrupt_register & EIR_MAPERR)) && ! (cpustate->diaglat)) + cpustate->mapper_address_latch = addr; + if ((! cpustate->reset_maperr) && ! (cpustate->error_interrupt_register & EIR_MAPERR)) + { + cpustate->error_interrupt_register |= EIR_MAPERR; + cpustate->write_inhibit = 1; + } + if (cpustate->write_inhibit) + (void)cpustate->program->read_word(addr); + else + cpustate->program->write_word(addr, data); + return; + } + if ((! (cpustate->error_interrupt_register & EIR_MAPERR)) && ! (cpustate->diaglat)) + cpustate->mapper_address_latch = cpustate->map_files[map_file].bias[map_index]+addr; + if ((cpustate->latch_control[map_index]) && (! cpustate->reset_maperr)) + cpustate->diaglat = 1; + cpustate->program->write_word(cpustate->map_files[map_file].bias[map_index]+addr, data); + } + } + + #define readbyte(cs, addr) readbyteX((cs), (addr), (cs)->cur_map) + static int readbyteX(tms99xx_state *cpustate, int addr, int map_file) + { + if ((map_file == 0) && (addr >= 0xf800)) + { /* intercept TPCS and CPU ROM */ + if (addr < 0xfc00) + /* TPCS */ + return cpustate->program->read_byte(0x1f0000+addr); + else + /* CPU ROM */ + return cpustate->program->read_byte(0x1f0000+addr); /* hack... */ + } + else if (! cpustate->mapping_on) + { + return cpustate->program->read_byte(addr); + } + else + { + int map_index; + + if (addr <= cpustate->map_files[map_file].limit[0]) + map_index = 0; + else if (addr <= cpustate->map_files[map_file].limit[1]) + map_index = 1; + else if (addr <= cpustate->map_files[map_file].limit[2]) + map_index = 2; + else + { /* mapping error */ + if ((! (cpustate->error_interrupt_register & EIR_MAPERR)) && ! (cpustate->diaglat)) + cpustate->mapper_address_latch = addr; + if ((! cpustate->reset_maperr) && ! (cpustate->error_interrupt_register & EIR_MAPERR)) + { + cpustate->error_interrupt_register |= EIR_MAPERR; + cpustate->write_inhibit = 1; + } + return cpustate->program->read_byte(addr); + } + if ((! (cpustate->error_interrupt_register & EIR_MAPERR)) && ! (cpustate->diaglat)) + cpustate->mapper_address_latch = cpustate->map_files[map_file].bias[map_index]+addr; + if ((cpustate->latch_control[map_index]) && (! cpustate->reset_maperr)) + cpustate->diaglat = 1; + return cpustate->program->read_byte(cpustate->map_files[map_file].bias[map_index]+addr); + } + } + + #define writebyte(cs, addr, data) writebyteX((cs), (addr), (data), (cs)->cur_map) + static void writebyteX(tms99xx_state *cpustate, int addr, int data, int map_file) + { + if ((map_file == 0) && (addr >= 0xf800)) + { /* intercept TPCS and CPU ROM */ + if (addr < 0xfc00) + /* TPCS */ + cpustate->program->write_byte(0x1f0000+addr, data); + else + /* CPU ROM */ + cpustate->program->write_byte(0x1f0000+addr, data); /* hack... */ + } + else if (! cpustate->mapping_on) + { + cpustate->program->write_byte(addr, data); + } + else + { + int map_index; + + if (addr <= cpustate->map_files[map_file].limit[0]) + map_index = 0; + else if (addr <= cpustate->map_files[map_file].limit[1]) + map_index = 1; + else if (addr <= cpustate->map_files[map_file].limit[2]) + map_index = 2; + else + { /* mapping error */ + if ((! (cpustate->error_interrupt_register & EIR_MAPERR)) && ! (cpustate->diaglat)) + cpustate->mapper_address_latch = addr; + if ((! cpustate->reset_maperr) && ! (cpustate->error_interrupt_register & EIR_MAPERR)) + { + cpustate->error_interrupt_register |= EIR_MAPERR; + cpustate->write_inhibit = 1; + } + if (cpustate->write_inhibit) + (void)cpustate->program->read_byte(addr); + else + cpustate->program->write_byte(addr, data); + return; + } + if ((! (cpustate->error_interrupt_register & EIR_MAPERR)) && ! (cpustate->diaglat)) + cpustate->mapper_address_latch = cpustate->map_files[map_file].bias[map_index]+addr; + if ((cpustate->latch_control[map_index]) && (! cpustate->reset_maperr)) + cpustate->diaglat = 1; + cpustate->program->write_byte(cpustate->map_files[map_file].bias[map_index]+addr, data); + } + } + +#elif (TMS99XX_MODEL == TMS9900_ID) || (TMS99XX_MODEL == TMS9940_ID) + /*16-bit data bus, 16-bit address bus (internal bus in the case of TMS9940)*/ + /*Note that tms9900 actually never accesses a single byte : when performing byte operations, + it reads a 16-bit word, changes the relevant byte, then write a complete word. You should + remember this when writing memory handlers.*/ + /*This does not apply to tms9995 and tms99xxx, but does apply to tms9980 (see below).*/ + + #define readword(cs, addr) (cs)->program->read_word(addr) + #define writeword(cs, addr,data) (cs)->program->write_word((addr), (data)) + + #define readbyte(cs, addr) (cs)->program->read_byte(addr) + #define writebyte(cs, addr,data) (cs)->program->write_byte((addr),(data)) + +#elif (TMS99XX_MODEL == TMS9980_ID) + /*8-bit data bus, 14-bit address*/ + /*Note that tms9980 never accesses a single byte (however crazy it may seem). Although this + makes memory access slower, I have emulated this feature, because if I did otherwise, + there would be some implementation problems in some driver sooner or later.*/ + + INLINE int readword(tms99xx_state *cpustate, int addr) + { + int val; + + cpustate->icount -= 2; + val = cpustate->program->read_byte(addr); + return (val << 8) | cpustate->program->read_byte(addr+1); + } + #define writeword(cs, addr,data) { (cs)->icount -= 2; (cs)->program->write_byte((addr), (data) >> 8); cpustate->program->write_byte((addr) + 1, (data) & 0xff); } + +#if 0 + #define readbyte(cs, addr) ((cs)->icount -= 2, (cs)->program->read_byte(addr)) + #define writebyte(cs, addr,data) { (cs)->icount -= 2; (cs)->program->write_byte((addr),(data)); } +#else + /*This is how it really works*/ + /*Note that every writebyte must match a readbyte (which is indeed the case)*/ + + static int readbyte(tms99xx_state *cpustate, int addr) + { + cpustate->icount -= 2; + if (addr & 1) + { + cpustate->extra_byte = cpustate->program->read_byte(addr-1); + return cpustate->program->read_byte(addr); + } + else + { + int val = cpustate->program->read_byte(addr); + cpustate->extra_byte = cpustate->program->read_byte(addr+1); + return val; + } + } + static void writebyte (tms99xx_state *cpustate, int addr, int data) + { + cpustate->icount -= 2; + if (addr & 1) + { + cpustate->program->write_byte(addr-1, cpustate->extra_byte); + cpustate->program->write_byte(addr, data); + } + else + { + cpustate->program->write_byte(addr, data); + cpustate->program->write_byte(addr+1, cpustate->extra_byte); + } + } +#endif + +#elif (TMS99XX_MODEL == TMS9985_ID) + /*Note that every writebyte must match a readbyte (which is indeed the case)*/ + + static int readword(tms99xx_state *cpustate, int addr) + { + if (addr < 0x2000) + { + } + else if ((addr >= 0x8300) && (addr < 0x8400)) + { + } + else + { + cpustate->icount -= 2; + return (cpustate->program->read_byte(addr) << 8) + cpustate->program->read_byte(addr + 1); + } + } + static void writeword(tms99xx_state *cpustate, int addr, int data) + { + if ((addr >= 0x8300) && (addr < 0x8400)) + { + } + else if (!(addr < 0x2000)) + { + cpustate->icount -= 2; + cpustate->program->write_byte(addr, data >> 8); + cpustate->program->write_byte(addr + 1, data & 0xff); + } + } + + static int readbyte(tms99xx_state *cpustate, int addr) + { + if (addr < 0x2000) + { + } + else if ((addr >= 0x8300) && (addr < 0x8400)) + { + } + else + { + cpustate->icount -= 2; + if (addr & 1) + { + cpustate->extra_byte = cpustate->program->read_byte(addr-1); + return cpustate->program->read_byte(addr); + } + else + { + int val = cpustate->program->read_byte(addr); + cpustate->extra_byte = cpustate->program->read_byte(addr+1); + return val; + } + } + } + static void writebyte(tms99xx_state *cpustate, int addr, int data) + { + if ((addr >= 0x8300) && (addr < 0x8400)) + { + } + else if (!(addr < 0x2000)) + { + cpustate->icount -= 2; + if (addr & 1) + { + cpustate->program->write_byte(addr-1, cpustate->extra_byte); + cpustate->program->write_byte(addr, data); + } + else + { + cpustate->program->write_byte(addr, data); + cpustate->program->write_byte(addr+1, cpustate->extra_byte); + } + } + } + +#elif (TMS99XX_MODEL == TMS9995_ID) + /*8-bit external data bus, with on-chip 16-bit RAM, and 16-bit address bus*/ + /*The code is complex, so we use functions rather than macros*/ + + static TIMER_CALLBACK( decrementer_callback ); + + static int readword(tms99xx_state *cpustate, int addr) + { + if ((addr < 0xf000) || (cpustate->is_mp9537)) + { + int reply; + cpustate->icount -= cpustate->memory_wait_states_word; + reply = cpustate->program->read_byte(addr); + return (reply << 8) | cpustate->program->read_byte(addr + 1); + } + else if (addr < 0xf0fc) + { + return *(UINT16 *)(& cpustate->RAM[addr - 0xf000]); + } + else if (addr < 0xfffa) + { + int reply; + cpustate->icount -= cpustate->memory_wait_states_word; + reply = cpustate->program->read_byte(addr); + return (reply << 8) | cpustate->program->read_byte(addr + 1); + } + else if (addr < 0xfffc) + { + /* read decrementer */ + if (cpustate->decrementer_enabled && !(cpustate->flag & 1)) + /* timer mode, timer enabled */ + return cpustate->device->attotime_to_cycles(cpustate->timer->remaining() / 16); + else + /* event counter mode or timer mode, timer disabled */ + return cpustate->decrementer_count; + } + else + { + return *(UINT16 *)(& cpustate->RAM[addr - 0xff00]); + } + } + + static void writeword (tms99xx_state *cpustate, int addr, int data) + { + if ((addr < 0xf000) || (cpustate->is_mp9537)) + { + cpustate->icount -= cpustate->memory_wait_states_word; + cpustate->program->write_byte(addr, data >> 8); + cpustate->program->write_byte(addr + 1, data & 0xff); + } + else if (addr < 0xf0fc) + { + *(UINT16 *)(& cpustate->RAM[addr - 0xf000]) = data; + } + else if (addr < 0xfffa) + { + cpustate->icount -= cpustate->memory_wait_states_word; + cpustate->program->write_byte(addr, data >> 8); + cpustate->program->write_byte(addr + 1, data & 0xff); + } + else if (addr < 0xfffc) + { + /* write decrementer */ + cpustate->decrementer_interval = data; + reset_decrementer(cpustate); + } + else + { + *(UINT16 *)(& cpustate->RAM[addr - 0xff00]) = data; + } + } + + static int readbyte(tms99xx_state *cpustate, int addr) + { + if ((addr < 0xf000) || (cpustate->is_mp9537)) + { + cpustate->icount -= cpustate->memory_wait_states_byte; + return cpustate->program->read_byte(addr); + } + else if (addr < 0xf0fc) + { + return cpustate->RAM[BYTE_XOR_BE(addr - 0xf000)]; + } + else if (addr < 0xfffa) + { + cpustate->icount -= cpustate->memory_wait_states_byte; + return cpustate->program->read_byte(addr); + } + else if (addr < 0xfffc) + { + /* read decrementer */ + int value; + + if (cpustate->decrementer_enabled && !(cpustate->flag & 1)) + /* timer mode, timer enabled */ + value = cpustate->device->attotime_to_cycles(cpustate->timer->remaining() / 16); + else + /* event counter mode or timer mode, timer disabled */ + value = cpustate->decrementer_count; + + if (addr & 1) + return (value & 0xFF); + else + return (value >> 8); + } + else + { + return cpustate->RAM[BYTE_XOR_BE(addr - 0xff00)]; + } + } + + static void writebyte (tms99xx_state *cpustate, int addr, int data) + { + if ((addr < 0xf000) || (cpustate->is_mp9537)) + { + cpustate->icount -= cpustate->memory_wait_states_byte; + cpustate->program->write_byte(addr, data); + } + else if (addr < 0xf0fc) + { + cpustate->RAM[BYTE_XOR_BE(addr - 0xf000)] = data; + } + else if (addr < 0xfffa) + { + cpustate->icount -= cpustate->memory_wait_states_byte; + cpustate->program->write_byte(addr, data); + } + else if (addr < 0xfffc) + { + /* write decrementer */ + /* Note that a byte write to tms9995 timer messes everything up. */ + cpustate->decrementer_interval = (data << 8) | data; + reset_decrementer(cpustate); + } + else + { + cpustate->RAM[BYTE_XOR_BE(addr - 0xff00)] = data; + } + } + +#else + + #error "memory access not implemented" + +#endif + +#if !HAS_MAPPING + #define readwordX(cs, addr, map_file) readword(cs, addr) + #define writewordX(cs, addr, data, map_file) writeword(cs, (addr), (data)) + #define readbyteX(cs, addr, map_file) readbyte(cs, addr) + #define writebyteX(cs, addr, data, map_file) writebyte(cs, (addr), (data)) +#endif + +#define READREG(reg) readword(cpustate, (cpustate->WP+(reg)) & 0xffff) +#define WRITEREG(reg, data) writeword(cpustate, (cpustate->WP+(reg)) & 0xffff, (data)) + +INLINE UINT16 READREG_DEBUG(tms99xx_state *cpustate, int reg) +{ + int temp = cpustate->icount; + UINT16 result = READREG(reg); + cpustate->icount = temp; + return result; +} + +INLINE void WRITEREG_DEBUG(tms99xx_state *cpustate, int reg, UINT16 data) +{ + int temp = cpustate->icount; + WRITEREG(reg, data); + cpustate->icount = temp; +} + +#if (TMS99XX_MODEL == TI990_10_ID) + READ8_HANDLER(ti990_10_mapper_cru_r) + { + tms99xx_state *cpustate = get_safe_token(&space.device()); + int reply = 0; + + switch(cpustate->mapper_cru_read_register) + { + case 0xb000: + reply = cpustate->map_files[cpustate->cur_map].B[0]; + break; + case 0xb001: + reply = cpustate->map_files[cpustate->cur_map].B[1]; + break; + case 0xb010: + reply = cpustate->map_files[cpustate->cur_map].B[2]; + break; + case 0xb011: + reply = cpustate->map_files[cpustate->cur_map].L[0]; + break; + case 0xb100: + reply = cpustate->map_files[cpustate->cur_map].L[1]; + break; + case 0xb101: + reply = cpustate->map_files[cpustate->cur_map].L[2]; + break; + case 0xb110: + reply = cpustate->mapper_address_latch; + break; + case 0xb111: + reply = cpustate->mapper_address_latch >> 16; + break; + } + + if (offset) + return (reply >> 8) & 0xff; + else + return reply & 0xff; + } + + WRITE8_HANDLER(ti990_10_mapper_cru_w) + { + tms99xx_state *cpustate = get_safe_token(&space.device()); + switch (offset) + { + case 0: + case 1: + case 2: + /* read register select */ + if (data) + cpustate->mapper_cru_read_register |= (1 << offset); + else + cpustate->mapper_cru_read_register &= ~ (1 << offset); + break; + case 3: + /* enable mapping */ + cpustate->mapping_on = data; + break; + case 4: + /* reset flags */ + cpustate->reset_maperr = data; + if (data) + { + cpustate->error_interrupt_register &= ~ EIR_MAPERR; + cpustate->diaglat = 0; + } + break; + case 5: + case 6: + case 7: + /* latch control */ + cpustate->latch_control[7-offset] = data; + break; + } + } + + INLINE void handle_error_interrupt(tms99xx_state *cpustate) + { + if (cpustate->error_interrupt_callback) + (*cpustate->error_interrupt_callback)(cpustate->device, cpustate->error_interrupt_register ? 1 : 0); + } + + READ8_HANDLER(ti990_10_eir_cru_r) + { + tms99xx_state *cpustate = get_safe_token(&space.device()); + return (offset == 1) ? (cpustate->error_interrupt_register & 0xff) : 0; + } + + WRITE8_HANDLER(ti990_10_eir_cru_w) + { + tms99xx_state *cpustate = get_safe_token(&space.device()); + if (offset < 4) /* does not work for EIR_MAPERR */ + { + cpustate->error_interrupt_register &= ~ (1 << offset); + + handle_error_interrupt(cpustate); + } + } + + +#endif + +/* Interrupt mask */ +#define IMASK (cpustate->STATUS & ST_IM) + +/* + CYCLES macro : you provide timings for tms9900 and tms9995, and the macro chooses for you. + + BTW, I have no idea what the timings are for tms9989 and tms99xxx... +*/ +#if TMS99XX_MODEL == TI990_10_ID + /* Use TI990/10 timings*/ + #define CYCLES(a,b,c) cpustate->icount -= a +#elif TMS99XX_MODEL <= TMS9985_ID + /* Use TMS9900/TMS9980 timings*/ + #define CYCLES(a,b,c) cpustate->icount -= b +#else + /* Use TMS9995 timings*/ + #define CYCLES(a,b,c) cpustate->icount -= c*4 +#endif + +#if (TMS99XX_MODEL == TMS9995_ID) + +static void set_flag0(tms99xx_state *cpustate, int val); +static void set_flag1(tms99xx_state *cpustate, int val); + +#endif + +/************************************************************************ + * Status register functions + ************************************************************************/ + +/* + remember that the OP ST bit is maintained in cpustate->lastparity +*/ + +/* + setstat sets the ST_OP bit according to cpustate->lastparity + + It must be called before reading the ST register. +*/ + +static void setstat(tms99xx_state *cpustate) +{ + int i; + UINT8 a; + + cpustate->STATUS &= ~ ST_OP; + + /* We set the parity bit. */ + a = cpustate->lastparity; + + for (i=0; i<8; i++) /* 8 bits to test */ + { + if (a & 1) /* If current bit is set */ + cpustate->STATUS ^= ST_OP; /* we toggle the ST_OP bit */ + + a >>= 1; /* Next bit. */ + } +} + +/* + getstat sets emulator's cpustate->lastparity variable according to 9900's STATUS bits. + It must be called on interrupt return, or when, for some reason, + the emulated program sets the STATUS register directly. +*/ +static void getstat(tms99xx_state *cpustate) +{ +#if (USE_ST_MASK) + cpustate->STATUS &= ST_MASK; /* unused bits are forced to 0 */ +#endif + + if (cpustate->STATUS & ST_OP) + cpustate->lastparity = 1; + else + cpustate->lastparity = 0; + +#if HAS_MAPPING + cpustate->cur_map = (cpustate->STATUS & ST_MF) ? 1 : 0; +#endif +} + +/* + A few words about the following functions. + + A big portability issue is the behavior of the ">>" instruction with the sign bit, which has + not been normalised. Every compiler does whatever it thinks smartest. + My code assumed that when shifting right signed numbers, the operand is left-filled with a + copy of sign bit, and that when shifting unsigned variables, it is left-filled with 0s. + This is probably the most logical behaviour, and it is the behavior of CW PRO3 - most time + (the exception is that ">>=" instructions always copy the sign bit (!)). But some compilers + are bound to disagree. + + So, I had to create special functions with predefined tables included, so that this code work + on every compiler. BUT this is a real slow-down. + So, you might have to include a few lines in assembly to make this work better. + Sorry about this, this problem is really unpleasant and absurd, but it is not my fault. +*/ + + +static const UINT16 right_shift_mask_table[17] = +{ + 0xFFFF, + 0x7FFF, + 0x3FFF, + 0x1FFF, + 0x0FFF, + 0x07FF, + 0x03FF, + 0x01FF, + 0x00FF, + 0x007F, + 0x003F, + 0x001F, + 0x000F, + 0x0007, + 0x0003, + 0x0001, + 0x0000 +}; + +static const UINT16 inverted_right_shift_mask_table[17] = +{ + 0x0000, + 0x8000, + 0xC000, + 0xE000, + 0xF000, + 0xF800, + 0xFC00, + 0xFE00, + 0xFF00, + 0xFF80, + 0xFFC0, + 0xFFE0, + 0xFFF0, + 0xFFF8, + 0xFFFC, + 0xFFFE, + 0xFFFF +}; + +INLINE UINT16 logical_right_shift(UINT16 val, int c) +{ + return((val>>c) & right_shift_mask_table[c]); +} + +INLINE INT16 arithmetic_right_shift(INT16 val, int c) +{ + if (val < 0) + return((val>>c) | inverted_right_shift_mask_table[c]); + else + return((val>>c) & right_shift_mask_table[c]); +} + + + + + +/* + Set lae +*/ +INLINE void setst_lae(tms99xx_state *cpustate, INT16 val) +{ + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ); + + if (val > 0) + cpustate->STATUS |= (ST_LGT | ST_AGT); + else if (val < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; +} + + +/* + Set laep (BYTE) +*/ +INLINE void setst_byte_laep(tms99xx_state *cpustate, INT8 val) +{ + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ); + + if (val > 0) + cpustate->STATUS |= (ST_LGT | ST_AGT); + else if (val < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; + + cpustate->lastparity = val; +} + +/* + For COC, CZC, and TB +*/ +INLINE void setst_e(tms99xx_state *cpustate, UINT16 val, UINT16 to) +{ + if (val == to) + cpustate->STATUS |= ST_EQ; + else + cpustate->STATUS &= ~ ST_EQ; +} + +/* + For CI, C, CB +*/ +INLINE void setst_c_lae(tms99xx_state *cpustate, UINT16 to, UINT16 val) +{ + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ); + + if (val == to) + cpustate->STATUS |= ST_EQ; + else + { + if ( ((INT16) val) > ((INT16) to) ) + cpustate->STATUS |= ST_AGT; + if ( ((UINT16) val) > ((UINT16) to) ) + cpustate->STATUS |= ST_LGT; + } +} + +/* + Set laeco for add +*/ +INLINE INT16 setst_add_laeco(tms99xx_state *cpustate, int a, int b) +{ + UINT32 res; + INT16 res2; + + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_C | ST_OV); + + res = (a & 0xffff) + (b & 0xffff); + + if (res & 0x10000) + cpustate->STATUS |= ST_C; + + if ((res ^ b) & (res ^ a) & 0x8000) + cpustate->STATUS |= ST_OV; + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + if (((a & b) | ((a | b) & ~ res)) & 0x0800) + cpustate->STATUS |= ST_DC; +#endif + + res2 = (INT16) res; + + if (res2 > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (res2 < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; + + return res2; +} + + +/* + Set laeco for subtract +*/ +INLINE INT16 setst_sub_laeco(tms99xx_state *cpustate, int a, int b) +{ + UINT32 res; + INT16 res2; + + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_C | ST_OV); + + res = (a & 0xffff) - (b & 0xffff); + + if (! (res & 0x10000)) + cpustate->STATUS |= ST_C; + + if ((a ^ b) & (a ^ res) & 0x8000) + cpustate->STATUS |= ST_OV; + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + if (((a & ~ b) | ((a | ~ b) & ~ res)) & 0x0800) + cpustate->STATUS |= ST_DC; +#endif + + res2 = (INT16) res; + + if (res2 > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (res2 < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; + + return res2; +} + + +/* + Set laecop for add (BYTE) +*/ +INLINE INT8 setst_addbyte_laecop(tms99xx_state *cpustate, int a, int b) +{ + unsigned int res; + INT8 res2; + + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_C | ST_OV | ST_OP); + + res = (a & 0xff) + (b & 0xff); + + if (res & 0x100) + cpustate->STATUS |= ST_C; + + if ((res ^ b) & (res ^ a) & 0x80) + cpustate->STATUS |= ST_OV; + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + if (((a & b) | ((a | b) & ~ res)) & 0x08) + cpustate->STATUS |= ST_DC; +#endif + + res2 = (INT8) res; + + if (res2 > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (res2 < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; + + cpustate->lastparity = res2; + + return res2; +} + + +/* + Set laecop for subtract (BYTE) +*/ +INLINE INT8 setst_subbyte_laecop(tms99xx_state *cpustate, int a, int b) +{ + unsigned int res; + INT8 res2; + + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_C | ST_OV | ST_OP); + + res = (a & 0xff) - (b & 0xff); + + if (! (res & 0x100)) + cpustate->STATUS |= ST_C; + + if ((a ^ b) & (a ^ res) & 0x80) + cpustate->STATUS |= ST_OV; + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + if (((a & ~ b) | ((a | ~ b) & ~ res)) & 0x08) + cpustate->STATUS |= ST_DC; +#endif + + res2 = (INT8) res; + + if (res2 > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (res2 < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; + + cpustate->lastparity = res2; + + return res2; +} + + + +/* + For NEG +*/ +INLINE void setst_laeo(tms99xx_state *cpustate, INT16 val) +{ + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_OV); + + if (val > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (val < 0) + { + cpustate->STATUS |= ST_LGT; + if (((UINT16) val) == 0x8000) + cpustate->STATUS |= ST_OV; + } + else + cpustate->STATUS |= ST_EQ; +} + + + +/* + Meat of SRA +*/ +INLINE UINT16 setst_sra_laec(tms99xx_state *cpustate, INT16 a, UINT16 c) +{ + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_C); + + if (c != 0) + { + a = arithmetic_right_shift(a, c-1); + if (a & 1) // The carry bit equals the last bit that is shifted out + cpustate->STATUS |= ST_C; + a = arithmetic_right_shift(a, 1); + } + + if (a > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (a < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; + + return a; +} + + +/* + Meat of SRL. Same algorithm as SRA, except that we fills in with 0s. +*/ +INLINE UINT16 setst_srl_laec(tms99xx_state *cpustate, UINT16 a,UINT16 c) +{ + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_C); + + if (c != 0) + { + a = logical_right_shift(a, c-1); + if (a & 1) + cpustate->STATUS |= ST_C; + a = logical_right_shift(a, 1); + } + + if (((INT16) a) > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (((INT16) a) < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; + + return a; +} + + +// +// Meat of SRC +// +INLINE UINT16 setst_src_laec(tms99xx_state *cpustate, UINT16 a,UINT16 c) +{ + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_C); + + if (c != 0) + { + a = logical_right_shift(a, c) | (a << (16-c)); + if (a & 0x8000) // The carry bit equals the last bit that is shifted out + cpustate->STATUS |= ST_C; + } + + if (((INT16) a) > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (((INT16) a) < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; + + return a; +} + + +// +// Meat of SLA +// +INLINE UINT16 setst_sla_laeco(tms99xx_state *cpustate, UINT16 a, UINT16 c) +{ + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_C | ST_OV); + + if (c != 0) + { + { + register UINT16 mask; + register UINT16 ousted_bits; + + mask = 0xFFFF << (16-c-1); + ousted_bits = a & mask; + + if (ousted_bits) // If ousted_bits is neither all 0s + if (ousted_bits ^ mask) // nor all 1s, + cpustate->STATUS |= ST_OV; // we set overflow + } + + a <<= c-1; + if (a & 0x8000) // The carry bit equals the last bit that is shifted out + cpustate->STATUS |= ST_C; + + a <<= 1; + } + + if (((INT16) a) > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (((INT16) a) < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; + + return a; +} + +/**************************************************************************/ + +static void register_for_save_state(device_t *device) +{ + tms99xx_state *cpustate = get_safe_token(device); + device->save_item(NAME(cpustate->WP)); + device->save_item(NAME(cpustate->PC)); + device->save_item(NAME(cpustate->STATUS)); + device->save_item(NAME(cpustate->interrupt_pending)); + +#if ! ((TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID)) + device->save_item(NAME(cpustate->load_state)); +#endif + +#if (TMS99XX_MODEL == TI990_10_ID) || (TMS99XX_MODEL == TMS9900_ID) || (TMS99XX_MODEL == TMS9980_ID) + device->save_item(NAME(cpustate->irq_level)); + device->save_item(NAME(cpustate->irq_state)); +#elif (TMS99XX_MODEL == TMS9995_ID) + device->save_item(NAME(cpustate->irq_level)); + device->save_item(NAME(cpustate->int_state)); + device->save_item(NAME(cpustate->int_latch)); +#endif + + device->save_item(NAME(cpustate->IDLE)); + +#if HAS_MAPPING + device->save_item(NAME(cpustate->mapping_on)); + device->save_item(NAME(cpustate->map_files[0].L)); + device->save_item(NAME(cpustate->map_files[0].B)); + device->save_item(NAME(cpustate->map_files[0].limit)); + device->save_item(NAME(cpustate->map_files[0].bias)); + device->save_item(NAME(cpustate->map_files[1].L)); + device->save_item(NAME(cpustate->map_files[1].B)); + device->save_item(NAME(cpustate->map_files[1].limit)); + device->save_item(NAME(cpustate->map_files[1].bias)); + device->save_item(NAME(cpustate->map_files[2].L)); + device->save_item(NAME(cpustate->map_files[2].B)); + device->save_item(NAME(cpustate->map_files[2].limit)); + device->save_item(NAME(cpustate->map_files[2].bias)); + device->save_item(NAME(cpustate->cur_map)); + device->save_item(NAME(cpustate->cur_src_map)); + device->save_item(NAME(cpustate->cur_dst_map)); + +#if (TMS99XX_MODEL == TI990_10_ID) + device->save_item(NAME(cpustate->reset_maperr)); + device->save_item(NAME(cpustate->mapper_address_latch)); + device->save_item(NAME(cpustate->mapper_cru_read_register)); + device->save_item(NAME(cpustate->diaglat)); + device->save_item(NAME(cpustate->latch_control)); +#endif +#endif + +#if (TMS99XX_MODEL == TI990_10_ID) + device->save_item(NAME(cpustate->error_interrupt_register)); +#endif + +#if (TMS99XX_MODEL == TMS9985_ID) || (TMS99XX_MODEL == TMS9995_ID) + device->save_item(NAME(cpustate->RAM)); +#endif + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) || (TMS99XX_MODEL == TMS9995_ID) + device->save_item(NAME(cpustate->decrementer_enabled)); + device->save_item(NAME(cpustate->decrementer_interval)); + device->save_item(NAME(cpustate->decrementer_count)); +#endif + +#if (TMS99XX_MODEL == TMS9995_ID) + device->save_item(NAME(cpustate->flag)); + device->save_item(NAME(cpustate->MID_flag)); + device->save_item(NAME(cpustate->memory_wait_states_byte)); + device->save_item(NAME(cpustate->memory_wait_states_word)); + device->save_item(NAME(cpustate->is_mp9537)); +#endif + + device->save_item(NAME(cpustate->disable_interrupt_recognition)); +} + + +static CPU_INIT( tms99xx ) +{ + const TMS99XX_RESET_PARAM *param = (const TMS99XX_RESET_PARAM *) device->static_config(); + tms99xx_state *cpustate = get_safe_token(device); + + register_for_save_state(device); + + cpustate->irq_level = 16; + cpustate->irq_callback = irqcallback; + cpustate->device = device; + cpustate->program = &device->space(AS_PROGRAM); + cpustate->io = &device->space(AS_IO); + +#if (TMS99XX_MODEL == TMS9995_ID) + cpustate->timer = device->machine().scheduler().timer_alloc(FUNC(decrementer_callback), cpustate); +#endif + + cpustate->idle_callback = param ? param->idle_callback : NULL; + #if (TMS99XX_MODEL == TI990_10_ID) + cpustate->rset_callback = param ? param->rset_callback : NULL; + cpustate->lrex_callback = param ? param->lrex_callback : NULL; + cpustate->ckon_ckof_callback = param ? param->ckon_ckof_callback : NULL; + + cpustate->error_interrupt_callback = param ? param->error_interrupt_callback : NULL; + #endif + +#if (TMS99XX_MODEL == TMS9995_ID) + /* we can ask at reset time that the CPU always generates one wait state automatically */ + if (param == NULL) + { /* if no param, the default is currently "wait state added" */ + cpustate->memory_wait_states_byte = 4; + cpustate->memory_wait_states_word = 12; + cpustate->is_mp9537 = 0; + } + else + { + cpustate->memory_wait_states_byte = (param->auto_wait_state) ? 4 : 0; + cpustate->memory_wait_states_word = (param->auto_wait_state) ? 12 : 4; + cpustate->is_mp9537 = param->is_mp9537; + } +#endif + +} + +/* + TMS9900 hard reset +*/ +static CPU_RESET( tms99xx ) +{ + tms99xx_state *cpustate = get_safe_token(device); + + cpustate->STATUS = 0; /* TMS9980 and TMS9995 Data Books say so */ + getstat(cpustate); + + #if HAS_MAPPING + cpustate->mapping_on = 0; + { + int i,j; + + for (i=0; i<3; i++) + for (j=0; j<3; j++) + { + cpustate->map_files[i].L[j] = 0; + cpustate->map_files[i].limit[j] = 0xffff; + cpustate->map_files[i].B[j] = 0; + cpustate->map_files[i].bias[j] = 0; + } + } + cpustate->cur_map = 0; /* equivalent to ST_MF status bit */ + cpustate->cur_src_map = 0; /* set to 2 by LDS */ + cpustate->cur_dst_map = 0; /* set to 2 by LDD */ + + cpustate->reset_maperr = 0; + #endif + + if (cpustate->IDLE) + { + cpustate->IDLE = 0; /* clear IDLE condition */ + if (cpustate->idle_callback) + (*cpustate->idle_callback)(device, 0); + } + +#if (TMS99XX_MODEL == TMS9995_ID) + cpustate->MID_flag = 0; + + /* Clear flag bits 0 & 1 */ + set_flag0(cpustate, 0); + set_flag1(cpustate, 0); + + /* Clear internal interrupt latches */ + cpustate->int_latch = 0; + cpustate->flag &= 0xFFE3; +#endif + + /* The ST register and interrupt latches changed, didn't they ? */ + field_interrupt(cpustate); + + contextswitchX(cpustate, 0x0000); + + CYCLES(6, 26, 14); +} + +static CPU_EXIT( tms99xx ) +{ + /* nothing to do ? */ +} + +/* fetch : read one word at * PC, and increment PC. */ +INLINE UINT16 fetch(tms99xx_state *cpustate) +{ + UINT16 value = readword(cpustate, cpustate->PC); + cpustate->PC += 2; + return value; +} + + +static CPU_EXECUTE( tms99xx ) +{ + tms99xx_state *cpustate = get_safe_token(device); + + cpustate->lds_flag = 0; + cpustate->ldd_flag = 0; + + do + { + /* all TMS9900 chips I know do not honor interrupts after XOP, BLWP or MID (after any + interrupt-like instruction, actually), and they do not either after LDS and LDD + (There are good reasons for this). */ + if ((cpustate->interrupt_pending) && (! cpustate->disable_interrupt_recognition)) + { + int level; + + level = cpustate->irq_level; + + + if (cpustate->load_state) + { /* LOAD has the highest priority */ + + contextswitchX(cpustate, 0xFFFC); /* load vector, save PC, WP and ST */ + + cpustate->STATUS &= ~ST_IM; /* clear interrupt mask */ + + /* clear IDLE status if necessary */ + if (cpustate->IDLE) + { + cpustate->IDLE = 0; /* clear IDLE condition */ + if (cpustate->idle_callback) + (*cpustate->idle_callback)(device, 0); + } + + CYCLES(6/*to be confirmed*/, 22, 14); + } + else if (level <= IMASK) + { /* a maskable interrupt is honored only if its level isn't greater than IMASK */ + + contextswitchX(cpustate, level*4); /* load vector, save PC, WP and ST */ + + /* change interrupt mask */ + if (level) + { + cpustate->STATUS = (cpustate->STATUS & ~ST_IM) | (level -1); /* decrement mask */ + cpustate->interrupt_pending = 0; /* as a consequence, the interrupt request will be subsequently ignored */ + } + else + cpustate->STATUS &= ~ST_IM; /* clear mask (is this correct???) */ + +#if (TMS99XX_MODEL == TMS9995_ID) + cpustate->STATUS &= 0xFE00; +#endif + + /* clear IDLE status if necessary */ + if (cpustate->IDLE) + { + cpustate->IDLE = 0; /* clear IDLE condition */ + if (cpustate->idle_callback) + (*cpustate->idle_callback)(device, 0); + } + +#if (TMS99XX_MODEL == TMS9995_ID) + /* Clear bit in latch */ + /* I think tms9989 does this, too */ + if (level != 2) + { /* Only do this on level 1, 3, 4 interrupts */ + int mask = 1 << level; + int flag_mask = (level == 1) ? 4 : mask; + + cpustate->int_latch &= ~ mask; + cpustate->flag &= ~ flag_mask; + + /* unlike tms9900, we can call the callback */ + if (level == 1) + (* cpustate->irq_callback)(cpustate->device, 0); + else if (level == 4) + (* cpustate->irq_callback)(cpustate->device, 1); + } +#endif + + CYCLES(6, 22, 14); + } + else + { + logerror("tms9900l.c : the interrupt_pending flag was set incorrectly\n"); + cpustate->interrupt_pending = 0; + } + } + + debugger_instruction_hook(device, cpustate->PC); + + if (cpustate->IDLE) + { /* IDLE instruction has halted execution */ + #if EXTERNAL_INSTRUCTION_DECODING + external_instruction_notify(cpustate, 2); + CYCLES(Moof!, 2, 2); /* 2 cycles per CRU write */ + #else + cpustate->icount = 0; /* much simpler... */ + #endif + } + else + { /* we execute an instruction */ + cpustate->disable_interrupt_recognition = 0; /* default value */ + cpustate->IR = fetch(cpustate); + execute(cpustate, cpustate->IR); + #if HAS_MAPPING + if (cpustate->lds_flag) + { + cpustate->lds_flag = 0; + cpustate->cur_src_map = 2 /*(cpustate->cur_src_map != 2) ? 2 : cpustate->cur_map*/; + } + else + cpustate->cur_src_map = cpustate->cur_map; + if (cpustate->ldd_flag) + { + cpustate->ldd_flag = 0; + cpustate->cur_dst_map = 2 /*(cpustate->cur_src_map != 2) ? 2 : cpustate->cur_map*/; + } + else + cpustate->cur_dst_map = cpustate->cur_map; + #if (TMS99XX_MODEL == TI990_10_ID) + cpustate->write_inhibit = 0; + #endif + #endif + + #if (HAS_OVERFLOW_INTERRUPT) + #if (TMS99XX_MODEL == TMS9995_ID) + /* Note that TI had some problem implementing this... I don't know if this feature works on + a real-world TMS9995. */ + if ((cpustate->STATUS & ST_OVIE) && (cpustate->STATUS & ST_OV) && (cpustate->irq_level > 2)) + cpustate->irq_level = 2; /* interrupt request */ + #else + #warning "todo..." + #endif + #endif + } + + } while (cpustate->icount > 0); +} + +#if (TMS99XX_MODEL == TI990_10_ID) + +static void tms99xx_set_irq_line(tms99xx_state *cpustate, int irqline, int state) +{ + if (irqline == INPUT_LINE_NMI) + { + cpustate->load_state = state; /* save new state */ + + field_interrupt(cpustate); /* interrupt status changed */ + } + else + { + /*if (cpustate->irq_state == state) + return;*/ + + cpustate->irq_state = state; + + if (state == CLEAR_LINE) + cpustate->irq_level = 16; + /* trick : 16 will always be bigger than the IM (0-15), so there will never be interrupts */ + else + cpustate->irq_level = (* cpustate->irq_callback)(cpustate->device, 0); + + field_interrupt(cpustate); /* interrupt state is likely to have changed */ + } +} + +#elif (TMS99XX_MODEL == TMS9900_ID) + +/* +void tms99xx_set_irq_line(cpustate, INPUT_LINE_NMI, int state) : change the state of the LOAD* line + + state == 0 -> LOAD* goes high (inactive) + state != 0 -> LOAD* goes low (active) + + While LOAD* is low, we keep triggering LOAD interrupts... + + A problem : some peripherals lower the LOAD* line for a fixed time interval (causing the 1st + instruction of the LOAD interrupt routine to be repeated while the line is low), and will be + perfectly happy with the current scheme, but others might be more clever and wait for the IAQ + (Instruction acquisition) line to go high, and this needs a callback function to emulate. +*/ + +/* +void tms99xx_set_irq_line(cpustate, int irqline, int state) : sets the state of the interrupt line. + + irqline is ignored, and should always be 0. + + state == 0 -> INTREQ* goes high (inactive) + state != 0 -> INTREQ* goes low (active) +*/ +/* + R Nabet 991020, revised 991218 : + In short : interrupt code should call "cpu_set_irq_line(0, 0, ASSERT_LINE);" to set an + interrupt request (level-triggered interrupts). Also, there MUST be a call to + "cpu_set_irq_line(0, 0, CLEAR_LINE);" in the machine code, when the interrupt line is released by + the hardware (generally in response to an action performed by the interrupt routines). + On tms9995 (9989 ?), you can use PULSE_LINE, too, since the processor latches the line... + + **Note** : HOLD_LINE *NEVER* makes sense on the TMS9900 (or 9980, 9995...). The reason is the + TMS9900 does NOT tell the world it acknoledges an interrupt, so no matter how much hardware you + use, you cannot know when the CPU takes the interrupt, hence you cannot release the line when + the CPU takes the interrupt. Generally, the interrupt condition is cleared by the interrupt + routine (with some CRU or memory access). + + Note that cpu_generate_interrupt uses HOLD_LINE, so your driver interrupt code + should always use the new style, i.e. return "ignore_interrupt(cpustate)" and call + "cpu_set_irq_line(0, 0, ASSERT_LINE);" explicitely. + + Last, many TMS9900-based hardware use a TMS9901 interrupt-handling chip. If anybody wants + to emulate some hardware which uses it, note that I am writing some emulation in the TI99/4(A) + driver in MESS, so you should ask me. +*/ +/* + * HJB 990430: changed to use irq_callback(cpustate) to retrieve the vector + * instead of using 16 irqlines. + * + * R Nabet 990830 : My mistake, I rewrote all these once again ; I think it is now correct. + * A driver using the TMS9900 should do : + * cpu_0_irq_line_vector_w(0, level); + * cpu_set_irq_line(0,0,ASSERT_LINE); + * + * R Nabet 991108 : revised once again, with advice from Juergen Buchmueller, after a discussion + * with Nicola... + * We use the callback to retreive the interrupt level as soon as INTREQ* is asserted. + * As a consequence, I do not support HOLD_LINE normally... However, we do not really have to + * support HOLD_LINE, since no real world TMS9900-based system can support this. + * FYI, there are two alternatives to retreiving the interrupt level with the callback : + * a) using 16 pseudo-IRQ lines. Mostly OK, though it would require a few core changes. + * However, this could cause some problems if someone tried to set two lines simulteanously... + * And TMS9900 did NOT have 16 lines ! This is why Juergen and I did not retain this solution. + * b) modifying the interrupt system in order to provide an extra int to every xxx_set_irq_line + * function. I think this solution would be fine, but it would require quite a number of + * changes in the MAME core. (And I did not feel the courage to check out 4000 drivers and 25 + * cpu cores ;-) .) + * + * Note that this does not apply to tms9995. +*/ +static void tms99xx_set_irq_line(tms99xx_state *cpustate, int irqline, int state) +{ + if (irqline == INPUT_LINE_NMI) + { + cpustate->load_state = state; /* save new state */ + + field_interrupt(cpustate); /* interrupt status changed */ + } + else + { + /*if (cpustate->irq_state == state) + return;*/ + + cpustate->irq_state = state; + + if (state == CLEAR_LINE) + cpustate->irq_level = 16; + /* trick : 16 will always be bigger than the IM (0-15), so there will never be interrupts */ + else + cpustate->irq_level = (* cpustate->irq_callback)(cpustate->device, 0); + + field_interrupt(cpustate); /* interrupt state is likely to have changed */ + } +} + +#elif (TMS99XX_MODEL == TMS9980_ID) +/* + interrupt system similar to tms9900, but only 3 interrupt pins (IC0-IC2) +*/ + +static void tms99xx_set_irq_line(tms99xx_state *cpustate, int irqline, int state) +{ + if (state == CLEAR_LINE) + { + cpustate->load_state = 0; + cpustate->irq_state = 0; + cpustate->irq_level = 16; + /* trick : 16 will always be bigger than the IM (0-15), so there will never be interrupts */ + } + else + { + int level; + + if (irqline == INPUT_LINE_NMI) + level = 2; /* translate MAME's convention to CPU's representation */ + else + level = (* cpustate->irq_callback)(cpustate->device, 0); + + switch (level) + { + case 0: + case 1: + cpustate->load_state = 0; + cpustate->irq_state = 0; + cpustate->irq_level = 16; + CPU_RESET_NAME(tms99xx)(cpustate->device); + break; + case 2: + cpustate->load_state = 1; + cpustate->irq_state = 0; + cpustate->irq_level = 16; + break; + case 7: + cpustate->load_state = 0; + cpustate->irq_state = 0; + cpustate->irq_level = 16; + break; + default: /* external levels 1, 2, 3, 4 */ + cpustate->load_state = 0; + cpustate->irq_state = 1; + cpustate->irq_level = level - 2; + break; + } + } + + field_interrupt(cpustate); /* interrupt state is likely to have changed */ +} + +#elif (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) +/* + 2 interrupt pins (int1 and int2) +*/ + +static void tms99xx_set_irq_line(tms99xx_state *cpustate, int irqline, int state) +{ + int mask; + + if (irqline == 0) + /* INT1 */ + mask = 1; + else if (irqline == 1) + /* INT2 */ + mask = 4; + else + /* What on earth??? */ + return; + + if (state) + cpustate->irq_state |= mask; + else + cpustate->irq_state &= ~mask; + + field_interrupt(cpustate); /* interrupt state is likely to have changed */ +} + +#elif (TMS99XX_MODEL == TMS9995_ID) +/* + this call-back is called by MESS timer system when the timer reaches 0. +*/ +static TIMER_CALLBACK( decrementer_callback ) +{ + tms99xx_state *cpustate = (tms99xx_state *)ptr; + + /* request decrementer interrupt */ + cpustate->int_latch |= 0x8; + cpustate->flag |= 0x8; + + field_interrupt(cpustate); +} + + +/* + reset and load the timer/decrementer + + Note that I don't know whether toggling flag0/flag1 causes the decrementer to be reloaded or not +*/ +static void reset_decrementer(tms99xx_state *cpustate) +{ + cpustate->timer->adjust(attotime::never); + + /* reload count */ + cpustate->decrementer_count = cpustate->decrementer_interval; + + /* decrementer / timer enabled ? */ + cpustate->decrementer_enabled = ((cpustate->flag & 2) && (cpustate->decrementer_interval)); + + if (cpustate->decrementer_enabled && ! (cpustate->flag & 1)) + { /* timer */ + attotime period = cpustate->device->cycles_to_attotime(cpustate->decrementer_interval * 16L); + cpustate->timer->adjust(period, 0, period); + } +} + +/* + You have two interrupt line : one triggers level-1 interrupts, the other triggers level-4 + interrupts (or decrements the decrementer register). + + According to the hardware, you may use PULSE_LINE (edge-triggered interrupts), or ASSERT_LINE + (level-triggered interrupts). Edge-triggered interrupts are way simpler, but if multiple devices + share the same line, they must use level-triggered interrupts. +*/ +static void tms99xx_set_irq_line(tms99xx_state *cpustate, int irqline, int state) +{ + if (irqline == INPUT_LINE_NMI) + { + cpustate->load_state = state; /* save new state */ + + field_interrupt(cpustate); /* interrupt status changed */ + } + else + { + int mask = (irqline == 0) ? 0x2 : 0x10; + int flag_mask = (irqline == 0) ? 0x4 : 0x10; + + if (((cpustate->int_state & mask) != 0) ^ (state != 0)) + { /* only if state changes */ + if (state) + { + cpustate->int_state |= mask; + + if ((irqline == 1) && (cpustate->flag & 1)) + { /* event counter mode : INT4* triggers no interrupt... */ + if (cpustate->decrementer_enabled) + { /* decrement, then interrupt if reach 0 */ + if ((-- cpustate->decrementer_count) == 0) + { + decrementer_callback(cpustate->device->machine(), cpustate, 0); + cpustate->decrementer_count = cpustate->decrementer_interval; /* reload */ + } + } + } + else + { /* plain interrupt mode */ + cpustate->int_latch |= mask; + cpustate->flag |= flag_mask; + } + } + else + { + cpustate->int_state &= ~ mask; + } + + field_interrupt(cpustate); /* interrupt status changed */ + } + } +} + +#else + +#error "interrupt system not implemented" + +#endif + +/* + * field_interrupt + * + * Determines whether if an interrupt is pending, and sets the relevant flag. + * + * Called when an interrupt pin (LOAD*, INTREQ*, IC0-IC3) is changed, and when the interrupt mask + * is modified. + * + * By using this flag, we save some compares in the execution loop. Subtle, isn't it ;-) ? + * + * R Nabet. + */ +#if (TMS99XX_MODEL == TI990_10_ID) || (TMS99XX_MODEL == TMS9900_ID) || (TMS99XX_MODEL == TMS9980_ID) + +static void field_interrupt(tms99xx_state *cpustate) +{ + cpustate->interrupt_pending = ((cpustate->irq_level <= IMASK) || (cpustate->load_state)); +} + +#elif (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + +static void field_interrupt(tms99xx_state *cpustate) +{ + if (cpustate->irq_state & 1) + cpustate->irq_level = 1; + else if (cpustate->irq_state & 2) + cpustate->irq_level = 2; + else if (cpustate->irq_state & 4) + cpustate->irq_level = 3; + else + cpustate->irq_level = 4; + /* trick : 4 will always be bigger than the IM (0-3), so there will never be interrupts */ + + cpustate->interrupt_pending = ((cpustate->irq_level <= IMASK) || (cpustate->load_state)); +} + +#elif (TMS99XX_MODEL == TMS9995_ID) + +static void field_interrupt(tms99xx_state *cpustate) +{ + if (cpustate->load_state) + { + cpustate->interrupt_pending = 1; + } + else + { + int current_int; + int level; + + if (cpustate->flag & 1) + /* event counter mode : ignore int4* line... */ + current_int = (cpustate->int_state & ~0x10) | cpustate->int_latch; + else + /* normal behavior */ + current_int = cpustate->int_state | cpustate->int_latch; + + if (current_int) + /* find first bit to 1 */ + /* possible values : 1, 3, 4 */ + for (level=0; ! (current_int & 1); current_int >>= 1, level++) + ; + else + level=16; + + cpustate->irq_level = level; + + cpustate->interrupt_pending = (level <= IMASK); + } +} + +#else + +#error "field_interrupt(cpustate) not written" + +#endif + +static CPU_DISASSEMBLE( tms99xx ) +{ + extern unsigned Dasm9900 (char *buffer, unsigned pc, int model_id, const UINT8 *oprom, const UINT8 *opram); + return Dasm9900(buffer, pc, TMS99XX_MODEL, oprom, opram); +} + + +/*****************************************************************************/ +/* + CRU support code + + The CRU bus is a 1-bit-wide I/O bus. The CPU can read or write bits at random address. + Special instructions are dedicated to reading and writing one or several consecutive bits. + + + + Note that TMS99000 additionally supports parallel CRU operations, although I don't know how + this feature is implemented. +*/ + +enum +{ +#if (TMS99XX_MODEL == TI990_10_ID) + /* 3 MSBs do exist, although they are not connected (don't ask...) */ + CRUAddrBit = 15 +#elif (TMS99XX_MODEL == TMS9900_ID) + /* 3 MSBs are always 0 to support external instructions */ + CRUAddrBit = 12 +#elif (TMS99XX_MODEL == TMS9980_ID) + /* 2 bits unused, and 2 MSBs are always 0 to support external instructions */ + CRUAddrBit = 11 +#elif (TMS99XX_MODEL == TMS9940_ID) + /* 9 internal address lines (8 external) */ + CRUAddrBit = 9 +#elif (TMS99XX_MODEL == TMS9995_ID) + /* no such problem here : data bus lines D0-D2 provide the external instruction code */ + CRUAddrBit = 15 +#else + #warning "I don't know how your processor handles CRU." + CRUAddrBit = 15 +#endif +}; + +enum +{ + rCRUAddrMask = (1 << (CRUAddrBit - 3)) - 1, + wCRUAddrMask = (1 << CRUAddrBit) - 1 +}; + + +#if (TMS99XX_MODEL == TMS9995_ID) + +/* set decrementer mode flag */ +static void set_flag0(tms99xx_state *cpustate, int val) +{ + if (val) + cpustate->flag |= 1; + else + cpustate->flag &= ~ 1; + + reset_decrementer(cpustate); +} + +/* set decrementer enable flag */ +static void set_flag1(tms99xx_state *cpustate, int val) +{ + if (val) + cpustate->flag |= 2; + else + cpustate->flag &= ~ 2; + + reset_decrementer(cpustate); +} + +#endif + +enum cru_error_code +{ + CRU_OK = 0, + CRU_PRIVILEGE_VIOLATION = -1 +}; + +#define WRITEPORT(cs, port, data) (cs)->io->write_byte(port, data) + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) +/* on tms9940, we have to handle internal CRU ports */ +static void write_single_CRU(tms99xx_state *cpustate, int port, int data) +{ + int mask; + + if (! (port & 0x100)) + { + /*if (cpustate->config & CB0)*/ + /* External CRU */ + WRITEPORT(cpustate, port, (data & 0x01)); + } + else + { + /* internal CRU */ + switch ((port >> 4) & 0xf) + { + case 0x8: + if (port == 0x181) + { + /* clear decrementer interrupt */ + cpustate->irq_state &= ~2; + } + if (port >= 0x183) && (port <= 0x186) + { + /* write configuration register */ + mask = 1 << (port - 0x183); + /* ... */ + } + break; + + case 0x9: + if (port <= 0x19D) + { + /* write decrementer latch */ + mask = 1 << (port - 0x190); + /* ... */ + } + else if (port == 0x19E) + { + /* set decrementer as timer (1) or event counter (0) */ + /* ... */ + } + break; + + case 0xA: + /* multiprocessor system interface */ + mask = 1 << (port - 0x1A0); + /* ... */ + break; + + case 0xB: + /* flags */ + mask = 1 << (port - 0x1B0); + /* ... */ + break; + + case 0xC: + case 0xD: + /* direction for P0-P31 */ + mask = 1 << (port - 0x1C0); + /* ... */ + break; + + case 0xE: + case 0xF: + /* data for P0-P31 */ + mask = 1 << (port - 0x1E0); + /* ... */ + break; + } + } +} +#elif (TMS99XX_MODEL == TMS9995_ID) +/* on tms9995, we have to handle internal CRU ports */ +static void write_single_CRU(tms99xx_state *cpustate, int port, int data) +{ + /* Internal CRU */ + switch (port) + { + case 0xF70: + set_flag0(cpustate, data & 0x01); + break; + case 0xF71: + set_flag1(cpustate, data & 0x01); + break; + case 0xF72: + case 0xF73: + case 0xF74: + break; /* ignored */ + case 0xF75: + case 0xF76: + case 0xF77: + case 0xF78: + case 0xF79: + case 0xF7A: + case 0xF7B: + case 0xF7C: + case 0xF7D: + case 0xF7E: + case 0xF7F: + { /* user defined flags */ + int mask = 1 << (port - 0xF70); + if (data & 0x01) + cpustate->flag |= mask; + else + cpustate->flag &= ~ mask; + } + break; + + case 0x0FED: + /* MID flag */ + cpustate->MID_flag = data & 0x01; + break; + } + /* External CRU */ + /* Even though all the registers above are implemented internally, accesses + are passed to the external bus, too, and an external device might respond + to a write to these CRU address as well (particularly a write to the user + flag registers). */ + WRITEPORT(cpustate, port, (data & 0x01)); +} +#else +#define write_single_CRU(cs, port, data) WRITEPORT(cs, port, data) +#endif + +/* + performs a normal write to CRU bus (used by SBZ, SBO, LDCR : address range 0 -> 0xFFF) +*/ +static cru_error_code writeCRU(tms99xx_state *cpustate, int CRUAddr, int Number, UINT16 Value) +{ + int count; + + /*logerror("PC %4.4x Write CRU %x for %x =%x\n",cpustate->PC,CRUAddr,Number,Value);*/ + + CRUAddr &= wCRUAddrMask; + + /* Write Number bits from CRUAddr */ + + for(count=0; count<Number; count++) + { + #if HAS_PRIVILEGE + if ((cpustate->STATUS & ST_PR) && (CRUAddr >= 0xE00)) + return CRU_PRIVILEGE_VIOLATION; + #endif + + write_single_CRU(cpustate, CRUAddr, (Value & 0x01)); + Value >>= 1; + CRUAddr = (CRUAddr + 1) & wCRUAddrMask; + } + + return CRU_OK; +} + +#if EXTERNAL_INSTRUCTION_DECODING +/* + Some opcodes perform a dummy write to a special CRU address, so that an external function may be + triggered. + + Only the first 3 MSBs of the address matter : other address bits and the written value itself + are undefined. + + How should we support this ? With callback functions ? Actually, as long as we do not support + hardware which makes use of this feature, it does not really matter :-) . +*/ +static void external_instruction_notify(tms99xx_state *cpustate, int ext_op_ID) +{ +#if 1 + /* I guess we can support this like normal CRU operations */ +#if (TMS99XX_MODEL == TMS9900_ID) + WRITEPORT(cpustate, ext_op_ID << 12, 0); /* or is it 1 ??? */ +#elif (TMS99XX_MODEL == TMS9980_ID) + WRITEPORT(cpustate, (ext_op_ID & 3) << 11, (ext_op_ID & 4) ? 1 : 0); +#elif (TMS99XX_MODEL == TMS9995_ID) + WRITEPORT(cpustate, ext_op_ID << 15, 0); /* or is it 1 ??? */ +#else + #warning "I don't know how your processor handles external opcodes (maybe you don't need them, though)." +#endif + +#else + switch (ext_op_ID) + { + case 2: /* IDLE */ + + break; + case 3: /* RSET */ + + break; + case 5: /* CKON */ + + break; + case 6: /* CKOF */ + + break; + case 7: /* LREX */ + + break; + case 0: + /* normal CRU write !!! */ + logerror("PC %4.4x : external_instruction_notify : wrong ext_op_ID",cpustate->PC); + break; + default: + /* unknown address */ + logerror("PC %4.4x : external_instruction_notify : unknown ext_op_ID",cpustate->PC); + break; + } +#endif +} +#endif + +/* + performs a normal read to CRU bus (used by TB, STCR : address range 0->0xFFF) + + Note that on some hardware, e.g. TI99/4(a), all normal memory operations cause unwanted CRU + read at the same address. This seems to be impossible to emulate efficiently, so, if you need + to emulate this, you're in trouble. +*/ +#define READPORT(cs, port) (cs)->io->read_byte(port) + + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) +/* on tms9940, we have to handle internal CRU ports */ +static int read_single_CRU(tms99xx_state *cpustate, int port) +{ + int reply; + int shift; + + if (! (port & 0x20)) + { + /*if (cpustate->config & CB0)*/ + /* External CRU */ + reply = READPORT(cpustate, port, (data & 0x01)); + } + else + { + /* internal CRU */ + switch (port) + { + case 0x10: + /* read interrupt state */ + reply = cpustate->irq_state; + break; + + case 0x12: + /* read decrementer LSB */ + /* ... */ + break; + case 0x13: + /* read decrementer MSB */ + /* ... */ + break; + + case 0x14: + /* read multiprocessor system interface LSB */ + /* ... */ + break; + case 0x15: + /* read multiprocessor system interface MSB */ + /* ... */ + break; + + case 0x16: + /* read flags LSB */ + /* ... */ + break; + case 0x17: + /* read flags MSB */ + /* ... */ + break; + + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + /* direction for P0-P31 */ + shift = (port - 0x18) << 3; + /* ... */ + break; + + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + /* data for P0-P31 */ + shift = (port - 0x1C) << 3; + /* ... */ + break; + + default: + reply = 0; + break; + } + } + + return reply; +} +#elif (TMS99XX_MODEL == TMS9995_ID) +/* on tms9995, we have to handle internal CRU ports */ +static int read_single_CRU(tms99xx_state *cpustate, int port) +{ + switch (port) + { + case 0x1EE: + /* flag, bits 0-7 */ + return cpustate->flag & 0xFF; + case 0x1EF: + /* flag, bits 8-15 */ + return (cpustate->flag >> 8) & 0xFF; + case 0x1FD: + /* MID flag, and external devices */ + if (cpustate->MID_flag) + return READPORT(cpustate, port) | 0x10; + else + return READPORT(cpustate, port) & ~ 0x10; + default: + /* external devices */ + return READPORT(cpustate, port); + } +} +#else +#define read_single_CRU(cs, port) READPORT(cs, port) +#endif + +static int readCRU(tms99xx_state *cpustate, int CRUAddr, int Number) +{ + static const int BitMask[] = + { + 0, /* filler - saves a subtract to find mask */ + 0x0001,0x0003,0x0007,0x000F,0x001F,0x003F,0x007F,0x00FF, + 0x01FF,0x03FF,0x07FF,0x0FFF,0x1FFF,0x3FFF,0x7FFF,0xFFFF + }; + + int Offset,Location,Value; + + /*logerror("Read CRU %x for %x\n",CRUAddr,Number);*/ + + Location = (CRUAddr >> 3) & rCRUAddrMask; + Offset = CRUAddr & 07; + + /* Read 8 bits */ + #if HAS_PRIVILEGE + if ((cpustate->STATUS & ST_PR) && (Location >= (0xE00 >> 3))) + return CRU_PRIVILEGE_VIOLATION; + #endif + Value = read_single_CRU(cpustate, Location); + + if ((Offset+Number) > 8) + { + /* Read next 8 bits */ + Location = (Location + 1) & rCRUAddrMask; + #if HAS_PRIVILEGE + if ((cpustate->STATUS & ST_PR) && (Location >= (0xE00 >> 3))) + return CRU_PRIVILEGE_VIOLATION; + #endif + Value |= read_single_CRU(cpustate, Location) << 8; + + if ((Offset+Number) > 16) + { + /* Read next 8 bits */ + Location = (Location + 1) & rCRUAddrMask; + #if HAS_PRIVILEGE + if ((cpustate->STATUS & ST_PR) && (Location >= (0xE00 >> 3))) + return CRU_PRIVILEGE_VIOLATION; + #endif + Value |= read_single_CRU(cpustate, Location) << 16; + } + } + + /* Allow for Offset */ + Value >>= Offset; + + /* Mask out what we want */ + Value &= BitMask[Number]; + + /* And update */ + return Value; +} + +/*****************************************************************************/ + +#if HAS_MAPPING +/* load a map file from memory */ +static void load_map_file(tms99xx_state *cpustate, UINT16 src_addr, int src_map_file, int dst_file) +{ + int i; + + + /* load mapped address into the memory address register */ + if ((src_map_file == 0) && (src_addr >= 0xf800)) + { /* intercept TPCS and CPU ROM */ + if (src_addr < 0xfc00) + /* TPCS */ + cpustate->mapper_address_latch = 0x1f0000+src_addr; + else + /* CPU ROM */ + cpustate->mapper_address_latch = 0x1f0000+src_addr; /* hack... */ + } + else if (! cpustate->mapping_on) + { + cpustate->mapper_address_latch = src_addr; + } + else + { + int map_index; + + if (src_addr <= cpustate->map_files[src_map_file].limit[0]) + map_index = 0; + else if (src_addr <= cpustate->map_files[src_map_file].limit[1]) + map_index = 1; + else if (src_addr <= cpustate->map_files[src_map_file].limit[2]) + map_index = 2; + else + { + if ((! cpustate->reset_maperr) && ! (cpustate->error_interrupt_register & EIR_MAPERR)) + { + cpustate->error_interrupt_register |= EIR_MAPERR; + cpustate->write_inhibit = 1; + } + cpustate->mapper_address_latch = src_addr; + map_index = -1; + } + if (map_index != -1) + cpustate->mapper_address_latch = cpustate->map_files[src_map_file].bias[map_index]+src_addr; + } + + + for (i=0; i<3; i++) + { + cpustate->map_files[dst_file].L[i] = cpustate->program->read_word(cpustate->mapper_address_latch) & 0xffe0; + cpustate->map_files[dst_file].limit[i] = (cpustate->map_files[dst_file].L[i] ^ 0xffe0) | 0x001f; + cpustate->mapper_address_latch = (cpustate->mapper_address_latch+2) & 0x1fffff; + cpustate->map_files[dst_file].B[i] = cpustate->program->read_word(cpustate->mapper_address_latch); + cpustate->map_files[dst_file].bias[i] = ((unsigned int) cpustate->map_files[dst_file].B[i]) << 5; + cpustate->mapper_address_latch = (cpustate->mapper_address_latch+2) & 0x1fffff; + } +} +#endif + +/* contextswitch : performs a BLWP, i.e. load PC, WP, and save old PC, old WP and ST... */ +static void contextswitch(tms99xx_state *cpustate, UINT16 addr) +{ + UINT16 oldWP, oldpc; + + /* save old state */ + oldWP = cpustate->WP; + oldpc = cpustate->PC; + + /* load vector */ + cpustate->WP = readword(cpustate, addr) & ~1; + cpustate->PC = readword(cpustate, addr+2) & ~1; + + /* write old state to regs */ + WRITEREG(R13, oldWP); + WRITEREG(R14, oldpc); + setstat(cpustate); + WRITEREG(R15, cpustate->STATUS); +} + +#if HAS_MAPPING || HAS_PRIVILEGE + +/* privileged context switch, that occurs after a reset, interrupt or XOP: +we enter privileged mode and select map file 0 before doing the context switch */ +/* For CPU that have no privilege support, contextswitchX would behave +identically to contextswitch, so we can call contextswitch in all cases. */ +static void contextswitchX(tms99xx_state *cpustate, UINT16 addr) +{ + UINT16 oldWP, oldpc, oldST; + + /* save old state */ + oldWP = cpustate->WP; + oldpc = cpustate->PC; + setstat(cpustate); + oldST = cpustate->STATUS; + + /* enter privileged mode and select map file 0 */ + #if HAS_PRIVILEGE + cpustate->STATUS &= ~ ST_PR; + #endif + #if HAS_MAPPING + cpustate->STATUS &= ~ ST_MF; + #endif + getstat(cpustate); + + /* load vector */ + cpustate->WP = readword(cpustate, addr) & ~1; + cpustate->PC = readword(cpustate, addr+2) & ~1; + + /* write old state to regs */ + WRITEREG(R13, oldWP); + WRITEREG(R14, oldpc); + WRITEREG(R15, oldST); +} + +#endif + +/* + * decipheraddr : compute and return the effective address in word instructions. + * + * NOTA : the LSBit is always ignored in word addresses, + * but we do not set it to 0 because of XOP... + */ +static UINT16 decipheraddr(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 ts = opcode & 0x30; + register UINT16 reg = opcode & 0xF; + + reg += reg; + + if (ts == 0) + /* Rx */ + return(reg + cpustate->WP); + else if (ts == 0x10) + { /* *Rx */ + CYCLES(0, 4, 1); + return(readword(cpustate, reg + cpustate->WP)); + } + else if (ts == 0x20) + { + register UINT16 imm; + + imm = fetch(cpustate); + + if (reg) + { /* @>xxxx(Rx) */ + CYCLES(1, 8, 3); + return(readword(cpustate, reg + cpustate->WP) + imm); + } + else + { /* @>xxxx */ + CYCLES(3, 8, 1); + return(imm); + } + } + else /*if (ts == 0x30)*/ + { /* *Rx+ */ + register UINT16 response; + + reg += cpustate->WP; /* reg now contains effective address */ + + CYCLES(1, 8, 3); + + response = readword(cpustate, reg); + writeword(cpustate, reg, response+2); /* we increment register content */ + return(response); + } +} + +/* decipheraddrbyte : compute and return the effective address in byte instructions. */ +static UINT16 decipheraddrbyte(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 ts = opcode & 0x30; + register UINT16 reg = opcode & 0xF; + + reg += reg; + + if (ts == 0) + /* Rx */ + return(reg + cpustate->WP); + else if (ts == 0x10) + { /* *Rx */ + CYCLES(0, 4, 1); + return(readword(cpustate, reg + cpustate->WP)); + } + else if (ts == 0x20) + { + register UINT16 imm; + + imm = fetch(cpustate); + + if (reg) + { /* @>xxxx(Rx) */ + CYCLES(1, 8, 3); + return(readword(cpustate, reg + cpustate->WP) + imm); + } + else + { /* @>xxxx */ + CYCLES(3, 8, 1); + return(imm); + } + } + else /*if (ts == 0x30)*/ + { /* *Rx+ */ + register UINT16 response; + + reg += cpustate->WP; /* reg now contains effective address */ + + CYCLES(1, 6, 3); + + response = readword(cpustate, reg); + writeword(cpustate, reg, response+1); /* we increment register content */ + return(response); + } +} + + +/*************************************************************************/ + +#if (TMS99XX_MODEL == TI990_10_ID) + /* TI990/10 generates an error interrupt */ + /* timings are unknown */ + #define HANDLE_ILLEGAL \ + { \ + cpustate->error_interrupt_register |= EIR_ILLOP; \ + if (cpustate->error_interrupt_callback) \ + (*cpustate->error_interrupt_callback)(cpustate->device, 1); \ + } +#elif TMS99XX_MODEL <= TMS9989_ID + /* TMS9900/TMS9980 merely ignore the instruction */ + #define HANDLE_ILLEGAL cpustate->icount -= 6 +#elif TMS99XX_MODEL == TMS9995_ID + /* TMS9995 generates a MID interrupt */ + #define HANDLE_ILLEGAL \ + { \ + cpustate->MID_flag = 1; \ + contextswitchX(cpustate, 0x0008); \ + cpustate->STATUS = (cpustate->STATUS & 0xFE00) | 0x1; \ + cpustate->disable_interrupt_recognition = 1; \ + } +#else +#define HANDLE_ILLEGAL +#warning "don't know" +#endif + +#if HAS_PRIVILEGE + #if (TMS99XX_MODEL == TI990_10_ID) + /* TI990/10 generates an error interrupt */ + /* timings are unknown */ + #define HANDLE_PRIVILEGE_VIOLATION \ + { \ + cpustate->error_interrupt_register |= EIR_PRIVOP; \ + if (cpustate->error_interrupt_callback) \ + (*cpustate->error_interrupt_callback)(cpustate->device, 1); \ + } + #else + #define HANDLE_PRIVILEGE_VIOLATION + #warning "don't know" + #endif +#endif + +/*========================================================================== + Illegal instructions >0000->01FF (not for 9989 and later) + >0C00->0FFF (not for 990/12 and 99110) +============================================================================*/ + +static void illegal(tms99xx_state *cpustate, UINT16 opcode) +{ + HANDLE_ILLEGAL; +} + + +#if 0 /* ti990/12 only */ +/*========================================================================== + Additionnal instructions, >0000->002F + Additionnal single-register instruction, >0030->003F + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | o p c o d e | + | o p c o d e | reg # | + --------------------------------- + +============================================================================*/ +static void h0000(tms99xx_state *cpustate, UINT16 opcode) +{ + if (opcode >= 0x30) + { /* STPC STore Program Counter */ + + } + else + { + switch (opcode /*& 0x3F*/) + { + case 0x1C: /* SRAM */ + /* SRAM -- Shift Right Arithmetic Multiple precision */ + case 0x1D: /* SLAM */ + /* SLAM -- Shift Left Arithmetic Multiple precision */ + case 0x1E: + /* RTO --- Right Test for Ones */ + case 0x1F: + /* LTO --- Left Test for Ones */ + case 0x20: + /* CNTO -- CouNT Ones */ + case 0x21: + /* SLSL -- Search LiSt Logical address */ + case 0x22: + /* SLSP -- Search LiSt Physical address */ + case 0x23: + /* BDC --- Binary to Decimal ascii Conversion */ + case 0x24: + /* DBC --- Decimal to Binary ascii Conversion */ + case 0x25: + /* SWPM -- SWaP Multiple precision */ + case 0x26: + /* XORM -- eXclusive OR Multiple precision */ + case 0x27: + /* ORM --- OR Multiple precision */ + case 0x28: + /* ANDM -- AND Multiple precision */ + case 0x29: /* SM */ + /* SM ---- Subtract Multiple precision integer */ + case 0x2A: /* AM */ + /* AM ---- Add Multiple precision integer */ + case 0x2B: + /* MOVA -- MOVe Address */ + case 0x2D: + /* EMD --- Execute Micro-Diagnostics */ + case 0x2E: + /* EINT -- Enable INTerrupts */ + case 0x2F: + /* DINT -- Disable INTerrupts */ + + break; + + default: + HANDLE_ILLEGAL; + break; + } + } +} +#endif + + +#if HAS_9995_OPCODES +/*========================================================================== + Additionnal single-register instructions, >0040->00FF + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | o p c o d e | reg # | + --------------------------------- + +tms9989 and later : LST, LWP +============================================================================*/ +static void h0040(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 addr; + + addr = opcode & 0xF; + addr = ((addr + addr) + cpustate->WP) & ~1; + + switch ((opcode & 0xF0) >> 4) + { + case 8: /* LST */ + /* LST --- Load STatus register */ + /* ST = *Reg */ + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + cpustate->STATUS = (cpustate->STATUS & 0x01DF) | (readword(cpustate, addr) & 0xFE20); + else + cpustate->STATUS = readword(cpustate, addr); + #else + cpustate->STATUS = readword(cpustate, addr); + #endif + getstat(cpustate); /* set last_parity */ + break; + + case 9: /* LWP */ + /* LWP --- Load Workspace Pointer */ + /* WP = *Reg */ + cpustate->WP = readword(cpustate, addr) & ~1; + break; + +#if 0 /* 990/12 opcodes */ + case 4: + /* CS ---- Compare Strings */ + case 5: + /* SEQB -- Search string for EQual Byte */ + case 6: + /* MOVS -- MOVe String */ + case 7: + /* LIM --- Load Interrupt Mask */ + + case 10: + /* LCS --- Load writable Control Store */ + + case 11: /* BLSK */ + /* BLSK -- Branch immediate and push Link to StacK */ + + case 12: + /* MVSR -- MoVe String Reverse */ + case 13: + /* MVSK -- MoVe string from StacK */ + case 14: + /* POPS -- POP String from stack */ + case 15: + /* PSHS -- PuSH String to stack */ + + break; +#endif + + default: + HANDLE_ILLEGAL; + break; + } +} + + +/*========================================================================== + Additionnal single-operand instructions, >0100->01FF + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | o p c o d e |TS | S | + --------------------------------- + +tms9989 and later : DIVS, MPYS +tms99xxx : BIND +============================================================================*/ +static void h0100(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 src; +#if HAS_MAPPING + int src_map = (opcode & 0x0030) ? cpustate->cur_src_map : cpustate->cur_map; +#endif + + src = decipheraddr(cpustate, opcode) & ~1; + + switch ((opcode & 0xC0) >> 6) + { +#if HAS_BIND_OPCODE + case 1: /* BIND */ + /* BIND -- Branch INDirect */ + cpustate->PC = readwordX(cpustate, src, src_map) & ~1; + CYCLES(Mooof!, Mooof!, 4 /*don't know*/); + break; +#endif + + case 2: /* DIVS */ + /* DIVS -- DIVide Signed */ + /* R0 = (R0:R1)/S R1 = (R0:R1)%S */ + { + INT16 d = readwordX(cpustate, src, src_map); + INT32 divq = (READREG(R0) << 16) | READREG(R1); + INT32 q = divq/d; + + if ((q < -32768L) || (q > 32767L)) + { + cpustate->STATUS |= ST_OV; + CYCLES(Mooof!, Mooof!, 10); + } + else + { + cpustate->STATUS &= ~ST_OV; + setst_lae(cpustate, q); + WRITEREG(R0, q); + WRITEREG(R1, divq%d); + /* tms9995 : 33 is the worst case */ + CYCLES(Mooof!, Mooof!, 33); + } + } + break; + + case 3: /* MPYS */ + /* MPYS -- MultiPlY Signed */ + /* Results: R0:R1 = R0*S */ + { + INT32 prod = ((INT32) (INT16) readwordX(cpustate, src, src_map)); + prod = prod*((INT32) (INT16) READREG(R0)); + + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ); + if (prod > 0) + cpustate->STATUS |= (ST_LGT | ST_AGT); + else if (prod < 0) + cpustate->STATUS |= ST_LGT; + else + cpustate->STATUS |= ST_EQ; + + WRITEREG(R0, prod >> 16); + WRITEREG(R1, prod); + } + CYCLES(Mooof!, Mooof!, 25); + break; + +#if 0 /* Origin unknown */ + case 0: /* EVAD */ + /* EVAD -- EValuate ADdress instruction */ + + break; +#endif + + default: + HANDLE_ILLEGAL; + break; + } +} +#endif + + +/*========================================================================== + Immediate, Control instructions, >0200->03FF + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | o p c o d e |0| reg # | + --------------------------------- + + LI, AI, ANDI, ORI, CI, STWP, STST, LIMI, LWPI, IDLE, RSET, RTWP, CKON, CKOF, LREX +systems with memory mapper: LMF +============================================================================*/ +static void h0200(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 addr; + register UINT16 value; /* used for anything */ + + addr = opcode & 0xF; + addr = ((addr + addr) + cpustate->WP) & ~1; + + #if HAS_MAPPING + if ((opcode >= 0x0320) && (opcode < 0x0340)) + { /* LMF */ + /* LMF --- Load memory Map File */ + /* Used by the memory mapper on ti990/10 with mapping option, ti990/12, and the TIM99610 + mapper chip to be associated with tms99000. + Syntax: "LMF Rn,m" loads map file m (0 or 1) with six words of memory, starting at address + specified in workspace register Rn (0 through 15). */ + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + { + HANDLE_PRIVILEGE_VIOLATION + return; + } + #endif + + /* read address pointer */ + addr = readword(cpustate, addr); + + load_map_file(cpustate, addr, cpustate->cur_map, (opcode & 0x10) ? 1 : 0); + + CYCLES(3, Mooof!, Mooof!); + return; + } + #endif + + #if BETTER_0200_DECODING + /* better instruction decoding on ti990/10 */ + if (opcode & 0x10) + { + HANDLE_ILLEGAL; + return; + } + #elif COMPLETE_0200_DECODING + /* full instruction decoding on tms9995 */ + if (((opcode < 0x2E0) && (opcode & 0x10)) || ((opcode >= 0x2E0) && (opcode & 0x1F))) + { +#if 0 + /* tms99110 opcode (not supported by 990/12) */ + if (opcode == 0x0301) + { /* CR ---- Compare Reals */ + } + else + /* tms99105+tms99110 opcode (not supported by 990/12) */ + if (opcode == 0x0302) + { /* MM ---- Multiply Multiple */ + } + else +#endif + #if 0 /* ti990/12 only */ + if (opcode >= 0x03F0) + { /* EP ---- Extended Precision */ + } + else + #endif + HANDLE_ILLEGAL; + return; + } + #endif + + switch ((opcode & 0x1e0) >> 5) + { + case 0: /* LI */ + /* LI ---- Load Immediate */ + /* *Reg = *PC+ */ + value = fetch(cpustate); + writeword(cpustate, addr, value); + setst_lae(cpustate, value); + CYCLES(3, 12, 3); + break; + + case 1: /* AI */ + /* AI ---- Add Immediate */ + /* *Reg += *PC+ */ + value = fetch(cpustate); + value = setst_add_laeco(cpustate, readword(cpustate, addr), value); + writeword(cpustate, addr, value); + CYCLES(4, 14, 4); + break; + + case 2: /* ANDI */ + /* ANDI -- AND Immediate */ + /* *Reg &= *PC+ */ + value = fetch(cpustate); + value = readword(cpustate, addr) & value; + writeword(cpustate, addr, value); + setst_lae(cpustate, value); + CYCLES(4, 14, 4); + break; + + case 3: /* ORI */ + /* ORI --- OR Immediate */ + /* *Reg |= *PC+ */ + value = fetch(cpustate); + value = readword(cpustate, addr) | value; + writeword(cpustate, addr, value); + setst_lae(cpustate, value); + CYCLES(4, 14, 4); + break; + + case 4: /* CI */ + /* CI ---- Compare Immediate */ + /* status = (*Reg-*PC+) */ + value = fetch(cpustate); + setst_c_lae(cpustate, value, readword(cpustate, addr)); + CYCLES(6, 14, 4); + break; + + case 5: /* STWP */ + /* STWP -- STore Workspace Pointer */ + /* *Reg = WP */ + writeword(cpustate, addr, cpustate->WP); + CYCLES(2, 8, 3); + break; + + case 6: /* STST */ + /* STST -- STore STatus register */ + /* *Reg = ST */ + setstat(cpustate); + writeword(cpustate, addr, cpustate->STATUS); + CYCLES(2, 8, 3); + break; + + case 7: /* LWPI */ + /* LWPI -- Load Workspace Pointer Immediate */ + /* WP = *PC+ */ + cpustate->WP = fetch(cpustate) & ~1; + CYCLES(3, 10, 4); + break; + + case 8: /* LIMI */ + /* LIMI -- Load Interrupt Mask Immediate */ + /* ST&15 |= (*PC+)&15 */ + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + { + HANDLE_PRIVILEGE_VIOLATION + break; + } + #endif + + value = fetch(cpustate); + cpustate->STATUS = (cpustate->STATUS & ~ST_IM) | (value & ST_IM); + field_interrupt(cpustate); /*IM has been modified.*/ + CYCLES(3, 16, 5); + break; + + case 9: /* LMF is implemented elsewhere - when it is implemented */ + HANDLE_ILLEGAL; + break; + + case 10: /* IDLE */ + /* IDLE -- IDLE until a reset, interrupt, load */ + /* The TMS99000 locks until an interrupt happen (like with 68k STOP instruction), + and continuously performs a special CRU write (code 2). */ + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + { + HANDLE_PRIVILEGE_VIOLATION + break; + } + #endif + cpustate->IDLE = 1; + #if EXTERNAL_INSTRUCTION_DECODING + external_instruction_notify(cpustate, 2); + #endif + if (cpustate->idle_callback) + (*cpustate->idle_callback)(cpustate->device, 1); + CYCLES(4, 12, 7); + /* we take care of further external_instruction_notify(cpustate, 2); in execute(cpustate) */ + break; + + case 12: /* RTWP */ + /* RTWP -- Return with Workspace Pointer */ + /* WP = R13, PC = R14, ST = R15 */ + addr = (cpustate->WP + R13) & ~1; + cpustate->WP = readword(cpustate, addr) & ~1; + addr += 2; + cpustate->PC = readword(cpustate, addr) & ~1; + addr += 2; + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + cpustate->STATUS = (cpustate->STATUS & 0x01DF) | (readword(cpustate, addr) & 0xFE20); + else + cpustate->STATUS = readword(cpustate, addr); + #else + cpustate->STATUS = readword(cpustate, addr); + #endif + getstat(cpustate); /* set last_parity */ + + field_interrupt(cpustate); /*IM has been modified.*/ + CYCLES(3, 14, 6); + break; + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + + case 11: /* RSET */ + case 13: /* CKON */ + case 14: /* CKOF */ + case 15: /* LREX */ + HANDLE_ILLEGAL; /* These instruction "have been deleted" on the TMS9940 */ + break; + +#else + + case 11: /* RSET */ + /* RSET -- ReSET */ + /* Reset the Interrupt Mask, and perform a special CRU write (code 3). */ + /* The CRU write is supposed to reset external devices. */ + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + { + HANDLE_PRIVILEGE_VIOLATION + break; + } + #endif + + cpustate->STATUS &= 0xFFF0; /*clear IM.*/ + field_interrupt(cpustate); /*IM has been modified.*/ + + #if (TMS99XX_MODEL == TMS9995_ID) + /*cpustate->MID_flag = 0;*/ /* not sure about this */ + #endif + #if (TMS99XX_MODEL == TI990_10_ID) + cpustate->error_interrupt_register = 0; + cpustate->mapping_on = 0; + + cpustate->reset_maperr = 0; + cpustate->mapper_cru_read_register = 0; + cpustate->latch_control[0] = cpustate->latch_control[1] = cpustate->latch_control[2] = 0; + + cpustate->diaglat = 0; + #endif + + #if EXTERNAL_INSTRUCTION_DECODING + external_instruction_notify(cpustate, 3); + #endif + + #if EXTERNAL_INSTRUCTION_CALLBACK + if (cpustate->rset_callback) + (*cpustate->rset_callback)(cpustate->device); + #endif + + CYCLES(5, 12, 7); + break; + +#if EXTERNAL_INSTRUCTION_DECODING + + case 13: /* CKON */ + case 14: /* CKOF */ + case 15: /* LREX */ + /* CKON -- ClocK ON */ + /* Perform a special CRU write (code 5). */ + /* An external circuitery could, for instance, enable the line clock interrupt (100Hz or 120Hz, depending on the electrical alimentation frequency). */ + /* CKOF -- ClocK OFf */ + /* Perform a special CRU write (code 6). */ + /* An external circuitery could, for instance, disable the line clock interrupt. */ + /* LREX -- Load or REstart eXecution */ + /* Perform a special CRU write (code 7). */ + /* An external circuitery could, for instance, activate the LOAD* line, + causing a non-maskable LOAD interrupt (vector -1). */ + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + { + HANDLE_PRIVILEGE_VIOLATION + break; + } + #endif + + external_instruction_notify(cpustate, (opcode & 0x00e0) >> 5); + + #if EXTERNAL_INSTRUCTION_CALLBACK + #warning "todo..." + #endif + + CYCLES(Mooof!, 12, 7); + break; + +#elif EXTERNAL_INSTRUCTION_CALLBACK + + case 13: /* CKON */ + case 14: /* CKOF */ + /* CKON -- ClocK ON */ + /* Enable the line clock interrupt (100Hz or 120Hz, depending on the electrical alimentation frequency). */ + /* CKOF -- ClocK OFf */ + /* Disable the line clock interrupt. */ + /* We use a callback because the line clock is implemented in machine + code, not in the CPU core. */ + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + { + HANDLE_PRIVILEGE_VIOLATION + break; + } + #endif + + if (cpustate->ckon_ckof_callback) + (*cpustate->ckon_ckof_callback)(cpustate->device, (opcode & 0x0020) ? 1 : 0); + + + CYCLES(5, Mooof!, Mooof!); + break; + + case 15: /* LREX */ + /* LREX -- Load or REstart eXecution */ + /* Trigger a LOAD interrupt (vector -1). (We use a callback, and I + have forgotten the reason why.) */ + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + { + HANDLE_PRIVILEGE_VIOLATION + break; + } + #endif + + if (cpustate->lrex_callback) + (*cpustate->lrex_callback)(cpustate->device); + + CYCLES(6, Mooof!, Mooof!); + break; + +#else + + #warning "Should not happen..." + +#endif + +#endif + } +} + + +/*========================================================================== + Single-operand instructions, >0400->07FF + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | o p c o d e |TS | S | + --------------------------------- + + BLWP, B, X, CLR, NEG, INV, INC, INCT, DEC, DECT, BL, SWPB, SETO, ABS +systems with memory mapper: LDD, LDS +============================================================================*/ +static void h0400(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 addr = decipheraddr(cpustate, opcode) & ~1; + register UINT16 value; /* used for anything */ +#if HAS_MAPPING + int src_map = (opcode & 0x0030) ? cpustate->cur_src_map : cpustate->cur_map; +#endif + + switch ((opcode & 0x3C0) >> 6) + { + case 0: /* BLWP */ + /* BLWP -- Branch and Link with Workspace Pointer */ + /* Result: WP = *S+, PC = *S */ + /* New R13=old WP, New R14=Old PC, New R15=Old ST */ + contextswitch(cpustate, addr); + CYCLES(3, 26, 11); + cpustate->disable_interrupt_recognition = 1; + break; + case 1: /* B */ + /* B ----- Branch */ + /* PC = S */ +#if ((TMS99XX_MODEL == TMS9900_ID) || (TMS99XX_MODEL == TMS9980_ID)) + (void) readwordX(cpustate, addr, src_map); +#endif + cpustate->PC = addr; + CYCLES(2, 8, 3); + break; + case 2: /* X */ + /* X ----- eXecute */ + /* Executes instruction *S */ + execute(cpustate, readwordX(cpustate, addr, src_map)); + /* On tms9900, the X instruction actually takes 8 cycles, but we gain 2 cycles on the next + instruction, as we don't need to fetch it. */ + CYCLES(1, 6, 2); + break; + case 3: /* CLR */ + /* CLR --- CLeaR */ + /* *S = 0 */ +#if ((TMS99XX_MODEL == TMS9900_ID) || (TMS99XX_MODEL == TMS9980_ID)) + (void) readwordX(cpustate, addr, src_map); +#endif + writewordX(cpustate, addr, 0, src_map); + CYCLES(2, 10, 3); + break; + case 4: /* NEG */ + /* NEG --- NEGate */ + /* *S = -*S */ + value = - (INT16) readwordX(cpustate, addr, src_map); + if (value) + cpustate->STATUS &= ~ ST_C; + else + cpustate->STATUS |= ST_C; + + #if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + if (value & 0x0FFF) + cpustate->STATUS &= ~ ST_DC; + else + cpustate->STATUS |= ST_DC; + #endif + + setst_laeo(cpustate, value); + writewordX(cpustate, addr, value, src_map); + CYCLES(3, 12, 3); + break; + case 5: /* INV */ + /* INV --- INVert */ + /* *S = ~*S */ + value = ~ readwordX(cpustate, addr, src_map); + writewordX(cpustate, addr, value, src_map); + setst_lae(cpustate, value); + CYCLES(2, 10, 3); + break; + case 6: /* INC */ + /* INC --- INCrement */ + /* (*S)++ */ + value = setst_add_laeco(cpustate, readwordX(cpustate, addr, src_map), 1); + writewordX(cpustate, addr, value, src_map); + CYCLES(2, 10, 3); + break; + case 7: /* INCT */ + /* INCT -- INCrement by Two */ + /* (*S) +=2 */ + value = setst_add_laeco(cpustate, readwordX(cpustate, addr, src_map), 2); + writewordX(cpustate, addr, value, src_map); + CYCLES(2, 10, 3); + break; + case 8: /* DEC */ + /* DEC --- DECrement */ + /* (*S)-- */ + value = setst_sub_laeco(cpustate, readwordX(cpustate, addr, src_map), 1); + writewordX(cpustate, addr, value, src_map); + CYCLES(2, 10, 3); + break; + case 9: /* DECT */ + /* DECT -- DECrement by Two */ + /* (*S) -= 2 */ + value = setst_sub_laeco(cpustate, readwordX(cpustate, addr, src_map), 2); + writewordX(cpustate, addr, value, src_map); + CYCLES(2, 10, 3); + break; + case 10: /* BL */ + /* BL ---- Branch and Link */ + /* IP=S, R11=old IP */ +#if ((TMS99XX_MODEL == TMS9900_ID) || (TMS99XX_MODEL == TMS9980_ID)) + (void) readwordX(cpustate, addr, src_map); +#endif + WRITEREG(R11, cpustate->PC); + cpustate->PC = addr; + CYCLES(3, 12, 5); + break; + case 11: /* SWPB */ + /* SWPB -- SWaP Bytes */ + /* *S = swab(*S) */ + value = readwordX(cpustate, addr, src_map); + value = logical_right_shift(value, 8) | (value << 8); + writewordX(cpustate, addr, value, src_map); + CYCLES(2, 10, 13); + break; + case 12: /* SETO */ + /* SETO -- SET to Ones */ + /* *S = #$FFFF */ +#if ((TMS99XX_MODEL == TMS9900_ID) || (TMS99XX_MODEL == TMS9980_ID)) + (void) readwordX(cpustate, addr, src_map); +#endif + writewordX(cpustate, addr, 0xFFFF, src_map); + CYCLES(2, 10, 3); + break; + case 13: /* ABS */ + /* ABS --- ABSolute value */ + /* *S = |*S| */ + /* clearing ST_C seems to be necessary, although ABS will never set it. */ +#if (TMS99XX_MODEL <= TMS9985_ID) + + /* ti990/10 and tms9900/tms9980 only write the result if it has changed */ + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_C | ST_OV); + + #if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + /* I guess ST_DC is cleared here, too*/ + cpustate->STATUS &= ~ ST_DC; + #endif + + value = readwordX(cpustate, addr, src_map); + + CYCLES(5, 12, Mooof!); + + if (((INT16) value) > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (((INT16) value) < 0) + { + cpustate->STATUS |= ST_LGT; + if (value == 0x8000) + cpustate->STATUS |= ST_OV; + + #if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + if (! (value & 0x0FFF)) + cpustate->STATUS |= ST_DC; + #endif + + writewordX(cpustate, addr, - ((INT16) value), src_map); + CYCLES(0, 2, Mooof!); + } + else + cpustate->STATUS |= ST_EQ; + + break; + +#else + + /* tms9995 always write the result */ + cpustate->STATUS &= ~ (ST_LGT | ST_AGT | ST_EQ | ST_C | ST_OV); + value = readwordX(cpustate, addr, src_map); + + CYCLES(Mooof!, Mooof!, 3); + if (((INT16) value) > 0) + cpustate->STATUS |= ST_LGT | ST_AGT; + else if (((INT16) value) < 0) + { + cpustate->STATUS |= ST_LGT; + if (value == 0x8000) + cpustate->STATUS |= ST_OV; + value = - ((INT16) value); + } + else + cpustate->STATUS |= ST_EQ; + + writewordX(cpustate, addr, value, src_map); + + break; + +#endif + + +#if HAS_MAPPING + /* Used by the memory mapper on ti990/10 with mapping option, ti990/12, and the TIM99610 + mapper chip to be associated with tms99000. */ + /* These opcode allow access to another page without the need of switching a page someplace. */ + /* Note that, if I read the 990/10 schematics correctly, two consecutive LDS or LDD would + cause some trouble. */ + case 14: /* LDS */ + /* LDS --- Long Distance Source */ + + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + { + HANDLE_PRIVILEGE_VIOLATION + break; + } + #endif + + load_map_file(cpustate, addr, src_map, 2); + cpustate->lds_flag = 1; + cpustate->disable_interrupt_recognition = 1; + break; + case 15: /* LDD */ + /* LDD --- Long Distance Destination */ + #if HAS_PRIVILEGE + if (cpustate->STATUS & ST_PR) + { + HANDLE_PRIVILEGE_VIOLATION + break; + } + #endif + + load_map_file(cpustate, addr, src_map, 2); + cpustate->ldd_flag = 1; + cpustate->disable_interrupt_recognition = 1; + break; + +#else + + default: + /* illegal instructions */ + HANDLE_ILLEGAL; + break; + +#endif + } +} + + +/*========================================================================== + Shift instructions, >0800->0BFF + -------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | o p c o d e | C | W | + --------------------------------- + + SRA, SRL, SLA, SRC +============================================================================*/ +static void h0800(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 addr; + register UINT16 cnt = (opcode & 0xF0) >> 4; + register UINT16 value; + + addr = (opcode & 0xF); + addr = ((addr+addr) + cpustate->WP) & ~1; + + CYCLES(3, 12, 5); + + if (cnt == 0) + { + CYCLES(2, 8, 2); + + cnt = READREG(R0) & 0xF; + + if (cnt == 0) + cnt = 16; + } + + CYCLES(cnt, cnt+cnt, cnt); + + switch ((opcode & 0x300) >> 8) + { + case 0: /* SRA */ + /* SRA --- Shift Right Arithmetic */ + /* *W >>= C (*W is filled on the left with a copy of the sign bit) */ + value = setst_sra_laec(cpustate, readword(cpustate, addr), cnt); + writeword(cpustate, addr, value); + break; + case 1: /* SRL */ + /* SRL --- Shift Right Logical */ + /* *W >>= C (*W is filled on the left with 0) */ + value = setst_srl_laec(cpustate, readword(cpustate, addr), cnt); + writeword(cpustate, addr, value); + break; + case 2: /* SLA */ + /* SLA --- Shift Left Arithmetic */ + /* *W <<= C */ + value = setst_sla_laeco(cpustate, readword(cpustate, addr), cnt); + writeword(cpustate, addr, value); + break; + case 3: /* SRC */ + /* SRC --- Shift Right Circular */ + /* *W = rightcircularshift(*W, C) */ + value = setst_src_laec(cpustate, readword(cpustate, addr), cnt); + writeword(cpustate, addr, value); + break; + } +} + + +#if 0 /* 990/12 opcodes */ +/*========================================================================== + Additionnal instructions, >0C00->0C0F + Additionnal single-register instructions, >0C10->0C3F + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | o p c o d e | + | o p c o d e | reg # | + --------------------------------- + +============================================================================*/ +static void h0c00(tms99xx_state *cpustate, UINT16 opcode) +{ + if (opcode & 0x30) + { + switch ((opcode & 0x30) >> 4) + { + case 1: + /* INSF -- INSert Field */ + break; + case 2: + /* XV ---- eXtract Value */ + break; + case 3: + /* XF ---- eXtract Field */ + break; + } + } + else + { + switch (opcode & 0x0F) + { + /* floating point instructions */ + case 0: + /* CRI --- Convert Real to Integer */ + break; + case 2: + /* NEGR -- NEGate Real */ + break; + case 4: + /* CRE --- Convert Real to Extended integer */ + break; + case 6: + /* CER --- Convert Extended integer to Real */ + break; + + case 1: + /* CDI --- Convert Double precision real to Integer */ + break; + case 3: + /* NEGD -- NEGate Double precision real */ + break; + case 5: + /* CDE --- Convert Double precision real to Extended integer */ + break; + case 7: + /* CED --- Convert Extended integer to Double precision real */ + break; + + case 8: + /* NRM --- NoRMalize */ + break; + + case 9: + /* TMB --- Test Memory Bit */ + break; + case 10: + /* TCMB -- Test and Clear Memory Bit */ + break; + case 11: + /* TSMB -- Test and Set Memory Bit */ + break; + + case 12: + /* SRJ --- Subtract from Register and Jump */ + case 13: + /* ARJ --- Add to Register and Jump */ + + case 14: + case 15: + /* XIT --- eXIT from floating point interpreter */ + /* Generated by some compilers, but equivalent to NOP on TI990/12. May have been used + by some software floating-point emulators. */ + break; + + default: + HANDLE_ILLEGAL; + break; + } + } +} + + +/*========================================================================== + Additionnal single-operand instructions, >0C40->0DFF + >0E40->0FFF + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | o p c o d e |TS | S | + --------------------------------- + +============================================================================*/ +static void h0c40(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 src; + +#if HAS_MAPPING + int src_map = (opcode & 0x0030) ? cpustate->cur_src_map : cpustate->cur_map; +#endif + + src = decipheraddr(cpustate, opcode) & ~1; + + switch ((opcode & 0x03C0) >> 6) + { + case 1: + /* AR ---- Add Real */ + break; + case 2: + /* CIR --- Convert Integer to Real */ + break; + case 3: + /* SR ---- Subtract Real */ + break; + case 4: + /* MR ---- Multiply Real */ + break; + case 5: + /* DR ---- Divide Real */ + break; + case 6: + /* LR ---- Load Real */ + break; + case 7: + /* STR --- STore Real */ + break; + case 9: + /* AD ---- Add Double */ + break; + case 10: + /* CID --- Convert Integer to Double */ + break; + case 11: + /* SD ---- Subtract Double */ + break; + case 12: + /* MD ---- Multiply Double */ + break; + case 13: + /* DD ---- Divide Double */ + break; + case 14: + /* LD ---- Load Double */ + break; + case 15: + /* SD ---- Store Double */ + break; + default: + HANDLE_ILLEGAL; + break; + } +} + +/*========================================================================== + Additionnal single-register instructions, >0E00->0E3F + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | o p c o d e | reg # | + --------------------------------- + +============================================================================*/ +static void h0e00(tms99xx_state *cpustate, UINT16 opcode) +{ + switch ((opcode & 0x30) >> 4) + { + case 1: + /* IOF --- Invert Order of Field */ + break; + case 1: + /* SNEB -- Search string for Not Equal Byte */ + break; + case 2: + /* CRC --- Cyclic Redundancy Code calculation */ + break; + case 3: + /* TS ---- Translate String */ + break; + } +} +#endif + + +/*========================================================================== + Jump, CRU bit instructions, >1000->1FFF + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | o p c o d e | signed offset | + --------------------------------- + + JMP, JLT, JLE, JEQ, JHE, JGT, JNE, JNC, JOC, JNO, JL, JH, JOP + SBO, SBZ, TB +============================================================================*/ +static void h1000(tms99xx_state *cpustate, UINT16 opcode) +{ + /* we convert 8 bit signed word offset to a 16 bit effective word offset. */ + register INT16 offset = ((INT8) opcode); + + + switch ((opcode & 0xF00) >> 8) + { + case 0: /* JMP */ + /* JMP --- unconditional JuMP */ + /* PC += offset */ + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + break; + case 1: /* JLT */ + /* JLT --- Jump if Less Than (arithmetic) */ + /* if (A==0 && EQ==0), PC += offset */ + if (! (cpustate->STATUS & (ST_AGT | ST_EQ))) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 2: /* JLE */ + /* JLE --- Jump if Lower or Equal (logical) */ + /* if (L==0 || EQ==1), PC += offset */ + if ((! (cpustate->STATUS & ST_LGT)) || (cpustate->STATUS & ST_EQ)) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 3: /* JEQ */ + /* JEQ --- Jump if EQual */ + /* if (EQ==1), PC += offset */ + if (cpustate->STATUS & ST_EQ) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 4: /* JHE */ + /* JHE --- Jump if Higher or Equal (logical) */ + /* if (L==1 || EQ==1), PC += offset */ + if (cpustate->STATUS & (ST_LGT | ST_EQ)) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 5: /* JGT */ + /* JGT --- Jump if Greater Than (arithmetic) */ + /* if (A==1), PC += offset */ + if (cpustate->STATUS & ST_AGT) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 6: /* JNE */ + /* JNE --- Jump if Not Equal */ + /* if (EQ==0), PC += offset */ + if (! (cpustate->STATUS & ST_EQ)) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 7: /* JNC */ + /* JNC --- Jump if No Carry */ + /* if (C==0), PC += offset */ + if (! (cpustate->STATUS & ST_C)) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 8: /* JOC */ + /* JOC --- Jump On Carry */ + /* if (C==1), PC += offset */ + if (cpustate->STATUS & ST_C) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 9: /* JNO */ + /* JNO --- Jump if No Overflow */ + /* if (OV==0), PC += offset */ + if (! (cpustate->STATUS & ST_OV)) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 10: /* JL */ + /* JL ---- Jump if Lower (logical) */ + /* if (L==0 && EQ==0), PC += offset */ + if (! (cpustate->STATUS & (ST_LGT | ST_EQ))) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 11: /* JH */ + /* JH ---- Jump if Higher (logical) */ + /* if (L==1 && EQ==0), PC += offset */ + if ((cpustate->STATUS & ST_LGT) && ! (cpustate->STATUS & ST_EQ)) + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + break; + case 12: /* JOP */ + /* JOP --- Jump On (odd) Parity */ + /* if (P==1), PC += offset */ + { + /* Let's set ST_OP. */ + int i; + UINT8 a; + a = cpustate->lastparity; + i = 0; + + while (a != 0) + { + if (a & 1) /* If current bit is set, */ + i++; /* increment bit count. */ + a >>= 1U; /* Next bit. */ + } + + /* Set ST_OP bit. */ + /*if (i & 1) + cpustate->STATUS |= ST_OP; + else + cpustate->STATUS &= ~ ST_OP;*/ + + /* Jump accordingly. */ + if (i & 1) /*(cpustate->STATUS & ST_OP)*/ + { + cpustate->PC += (offset + offset); + CYCLES(3, 10, 3); + } + else + CYCLES(2, 8, 3); + } + + break; + case 13: /* SBO */ + /* SBO --- Set Bit to One */ + /* CRU Bit = 1 */ + #if HAS_PRIVILEGE + if (writeCRU(cpustate, (READREG(R12) >> 1) + offset, 1, 1) == CRU_PRIVILEGE_VIOLATION) + HANDLE_PRIVILEGE_VIOLATION + #else + writeCRU(cpustate, (READREG(R12) >> 1) + offset, 1, 1); + #endif + + CYCLES(4, 12, 8); + break; + + case 14: /* SBZ */ + /* SBZ --- Set Bit to Zero */ + /* CRU Bit = 0 */ + #if HAS_PRIVILEGE + if (writeCRU(cpustate, (READREG(R12) >> 1) + offset, 1, 0) == CRU_PRIVILEGE_VIOLATION) + HANDLE_PRIVILEGE_VIOLATION + #else + writeCRU(cpustate, (READREG(R12) >> 1) + offset, 1, 0); + #endif + + CYCLES(4, 12, 8); + break; + + case 15: /* TB */ + /* TB ---- Test Bit */ + /* EQ = (CRU Bit == 1) */ + #if HAS_PRIVILEGE + { + int value; + + value = readCRU(cpustate, (READREG(R12)>> 1) + offset, 1); + if (value == CRU_PRIVILEGE_VIOLATION) + HANDLE_PRIVILEGE_VIOLATION + else + setst_e(cpustate, value & 1, 1); + } + #else + setst_e(cpustate, readCRU(cpustate, (READREG(R12)>> 1) + offset, 1) & 1, 1); + #endif + CYCLES(5, 12, 8); + break; + } +} + + +/*========================================================================== + General and One-Register instructions >2000->3FFF + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + --------------------------------- + | opcode | D |TS | S | + --------------------------------- + + COC, CZC, XOR, LDCR, STCR, XOP, MPY, DIV +tms9940 : DCA, DCS, LIIM +==========================================================================*/ + +/* xop, ldcr and stcr are handled elsewhere */ +static void h2000(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 dest = (opcode & 0x3C0) >> 6; + register UINT16 src; + register UINT16 value; + +#if HAS_MAPPING + int src_map = (opcode & 0x0030) ? cpustate->cur_src_map : cpustate->cur_map; +#endif + + src = decipheraddr(cpustate, opcode) & ~1; + dest = ((dest+dest) + cpustate->WP) & ~1; + + switch ((opcode & 0x1C00) >> 10) + { + case 0: /* COC */ + /* COC --- Compare Ones Corresponding */ + /* status E bit = (S&D == S) */ + value = readwordX(cpustate, src, src_map); + setst_e(cpustate, value & readword(cpustate, dest), value); + CYCLES(5, 14, 4); + break; + case 1: /* CZC */ + /* CZC --- Compare Zeroes Corresponding */ + /* status E bit = (S&~D == S) */ + value = readwordX(cpustate, src, src_map); + setst_e(cpustate, value & (~ readword(cpustate, dest)), value); + CYCLES(5, 14, 4); + break; + case 2: /* XOR */ + /* XOR --- eXclusive OR */ + /* D ^= S */ + value = readwordX(cpustate, src, src_map); + value ^= readword(cpustate, dest); + setst_lae(cpustate, value); + writeword(cpustate, dest,value); + CYCLES(3, 14, 4); + break; + /*case 3:*/ /* XOP is implemented elsewhere */ + /*case 4:*/ /* LDCR is implemented elsewhere */ + /*case 5:*/ /* STCR is implemented elsewhere */ + case 6: /* MPY */ + /* MPY --- MultiPlY (unsigned) */ + /* Results: D:D+1 = D*S */ + /* Note that early TMS9995 reportedly performs an extra dummy read in PC space */ + { + unsigned long prod = ((unsigned long) readwordX(cpustate, src, src_map)); + prod = prod * ((unsigned long) readword(cpustate, dest)); + writeword(cpustate, dest, prod >> 16); + writeword(cpustate, (dest+2)&0xffff, prod); + } + /* ti990/10 : from 19 to 35, possibly 19 + (number of bits to 1 in one operand) */ + CYCLES(35, 52, 23); + break; + case 7: /* DIV */ + /* DIV --- DIVide (unsigned) */ + /* D = D/S D+1 = D%S */ + { + UINT16 d = readwordX(cpustate, src, src_map); + UINT16 hi = readword(cpustate, dest); + unsigned long divq = (((unsigned long) hi) << 16) | readword(cpustate, (dest+2)&0xffff); + + if (d <= hi) + { + cpustate->STATUS |= ST_OV; + CYCLES(4, 16, 6); + } + else + { + cpustate->STATUS &= ~ST_OV; + writeword(cpustate, dest, divq/d); + writeword(cpustate, (dest+2)&0xffff, divq%d); + /* tms9900 : from 92 to 124, possibly 92 + 2*(number of bits to 1 (or 0?) in quotient) */ + /* tms9995 : 28 is the worst case */ + /* ti990/10 : from 41 to 58, possibly 41 + (number of bits to 1 (or 0?) in quotient) */ + CYCLES(41, 92, 28); + } + } + break; + } +} + +static void xop(tms99xx_state *cpustate, UINT16 opcode) +{ /* XOP */ + /* XOP --- eXtended OPeration */ + /* WP = *(40h+D), PC = *(42h+D) */ + /* New R13=old WP, New R14=Old IP, New R15=Old ST */ + /* New R11=S */ + /* Xop bit set */ + + register UINT16 immediate = (opcode & 0x3C0) >> 6; + register UINT16 operand; + + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + switch (immediate) + { + case 0: /* DCA */ + /* DCA --- Decimal Correct Addition */ + operand = decipheraddrbyte(cpustate, opcode); + { + int value = readbyte(cpustate, operand); + int X = (value >> 4) & 0xf; + int Y = value & 0xf; + + if (Y >= 10) + { + Y -= 10; + cpustate->STATUS |= ST_DC; + X++; + } + else if (cpustate->STATUS & ST_DC) + { + Y += 6; + } + + if (X >= 10) + { + X -= 10; + cpustate->STATUS |= ST_C; + } + else if (cpustate->STATUS & ST_C) + { + X += 6; + } + + writebyte(cpustate, operand, (X << 4) | Y); + } + break; + case 1: /* DCS */ + /* DCS --- Decimal Correct Substraction */ + operand = decipheraddrbyte(cpustate, opcode); + { + int value = readbyte(cpustate, operand); + + if (! (cpustate->STATUS & ST_DC)) + { + value += 10; + } + + if (! (cpustate->STATUS & ST_C)) + { + value += 10 << 4; + } + + cpustate->STATUS ^= ST_DC; + + writebyte(cpustate, operand, value); + } + break; + case 2: /* LIIM */ + case 3: /* LIIM */ + /* LIIM - Load Immediate Interrupt Mask */ + /* Does the same job as LIMI, with a different opcode format. */ + /* Note that, unlike TMS9900, the interrupt mask is only 2-bit long. */ + (void)decipheraddr(cpustate, opcode); /* dummy decode (personnal guess) */ + + cpustate->STATUS = (cpustate->STATUS & ~ST_IM) | (opcode & ST_IM); + break; + default: /* normal XOP */ +#endif + + /* TODO : emulate 990/10 hardware XOP */ + operand = decipheraddr(cpustate, opcode); + + #if ((TMS99XX_MODEL <= TMS9989_ID) && (TMS99XX_MODEL != TI990_10_ID)) + (void)readword(cpustate, operand & ~1); /*dummy read (personnal guess)*/ + #endif + + contextswitchX(cpustate, 0x40 + (immediate << 2)); + + #if ! ((TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID)) + /* The bit is not set on tms9940 */ + cpustate->STATUS |= ST_X; + #endif + + WRITEREG(R11, operand); + CYCLES(7, 36, 15); + cpustate->disable_interrupt_recognition = 1; + +#if (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) + break; + } +#endif +} + +/* LDCR and STCR */ +static void ldcr_stcr(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 cnt = (opcode & 0x3C0) >> 6; + register UINT16 addr; + int value; + +#if HAS_MAPPING + int src_map = (opcode & 0x0030) ? cpustate->cur_src_map : cpustate->cur_map; +#endif + + if (cnt == 0) + cnt = 16; + + if (cnt <= 8) + addr = decipheraddrbyte(cpustate, opcode); + else + addr = decipheraddr(cpustate, opcode) & ~1; + + if (opcode < 0x3400) + { /* LDCR */ + /* LDCR -- LoaD into CRu */ + /* CRU R12--CRU R12+D-1 set to S */ + if (cnt <= 8) + { +#if (TMS99XX_MODEL != TMS9995_ID) + value = readbyteX(cpustate, addr, src_map); +#else + /* just for once, tms9995 behaves like earlier 8-bit tms99xx chips */ + /* this must be because instruction decoding is too complex */ + value = readwordX(cpustate, addr & ~1, src_map); + if (addr & 1) + value &= 0xFF; + else + value = (value >> 8) & 0xFF; +#endif + + (void)READREG(cnt+cnt); /*dummy read (reasonnable guess for TMS9995 & TMS9900, ti990/10)*/ + + setst_byte_laep(cpustate, value); + } + else + { + value = readwordX(cpustate, addr, src_map); + + (void)READREG(cnt+cnt); /*dummy read (reasonnable guess for TMS9995 & TMS9900, ti990/10)*/ + + setst_lae(cpustate, value); + } + + #if HAS_PRIVILEGE + if (writeCRU(cpustate, (READREG(R12) >> 1), cnt, value) == CRU_PRIVILEGE_VIOLATION) + HANDLE_PRIVILEGE_VIOLATION + #else + writeCRU(cpustate, (READREG(R12) >> 1), cnt, value); + #endif + + CYCLES(4+cnt, 20 + cnt+cnt, 9 + cnt+cnt); + } + else + { /* STCR */ + /* STCR -- STore from CRu */ + /* S = CRU R12--CRU R12+D-1 */ + if (cnt <= 8) + { +#if (TMS99XX_MODEL != TMS9995_ID) + + (void)READREG(cnt+cnt); /*dummy read (reasonable guess for TMS9995 & TMS9900, ti990/10)*/ + // MZ: Read before write + int value2 = readwordX(cpustate, addr & ~1, src_map); + + #if HAS_PRIVILEGE + value = readCRU(cpustate, (READREG(R12) >> 1), cnt); + + if (value == CRU_PRIVILEGE_VIOLATION) + HANDLE_PRIVILEGE_VIOLATION + else + { + setst_byte_laep(cpustate, value); + writewordX(cpustate, addr, ((value << 8) & 0xff00) | (value2 & 0x00ff), src_map); + } + #else + value = readCRU(cpustate, (READREG(R12) >> 1), cnt); + setst_byte_laep(cpustate, value); + + writewordX(cpustate, addr, ((value << 8) & 0xff00) | (value2 & 0x00ff), src_map); + #endif + CYCLES(18+cnt, (cnt != 8) ? 42 : 44, 19 + cnt); +#else + /* just for once, tms9995 behaves like earlier 8-bit tms99xx chips */ + /* this must be because instruction decoding is too complex */ + int value2 = readwordX(cpustate, addr & ~1, src_map); + + (void)READREG(cnt+cnt); /*dummy read (reasonable guess for TMS9995 & TMS9900, ti990/10)*/ + + value = readCRU(cpustate, (READREG(R12) >> 1), cnt); + setst_byte_laep(cpustate, value); + + if (addr & 1) + writewordX(cpustate, addr & ~1, (value & 0x00FF) | (value2 & 0xFF00), src_map); + else + writewordX(cpustate, addr & ~1, (value2 & 0x00FF) | ((value << 8) & 0xFF00), src_map); + + CYCLES(Mooof!, Mooof!, 19 + cnt); +#endif + } + else + { + (void)readwordX(cpustate, addr, src_map); /*dummy read*/ + + (void)READREG(cnt+cnt); /*dummy read (reasonnable guess for TMS9995 & TMS9900, ti990/10)*/ + + #if HAS_PRIVILEGE + value = readCRU(cpustate, (READREG(R12) >> 1), cnt); + if (value == CRU_PRIVILEGE_VIOLATION) + HANDLE_PRIVILEGE_VIOLATION + else + { + setst_lae(cpustate, value); + writewordX(cpustate, addr, value, src_map); + } + #else + value = readCRU(cpustate, (READREG(R12) >> 1), cnt); + setst_lae(cpustate, value); + writewordX(cpustate, addr, value, src_map); + #endif + CYCLES(24+cnt, (cnt != 16) ? 58 : 60, 27 + cnt); + } + } +} + + +/*========================================================================== + Two-Operand instructions >4000->FFFF + --------------------------------------------------------------------------- + + 0 1 2 3-4 5 6 7+8 9 A B-C D E F + ---------------------------------- + |opcode|B|TD | D |TS | S | + ---------------------------------- + + SZC, SZCB, S, SB, C, CB, A, AB, MOV, MOVB, SOC, SOCB +============================================================================*/ + +/* word instructions */ +static void h4000w(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 src; + register UINT16 dest; + register UINT16 value; + +#if HAS_MAPPING + int src_map = (opcode & 0x0030) ? cpustate->cur_src_map : cpustate->cur_map; + int dst_map = (opcode & 0x0c00) ? cpustate->cur_dst_map : cpustate->cur_map; +#endif + + src = decipheraddr(cpustate, opcode) & ~1; + dest = decipheraddr(cpustate, opcode >> 6) & ~1; + + value = readwordX(cpustate, src, src_map); + + switch ((opcode >> 13) & 0x0007) /* ((opcode & 0xE000) >> 13) */ + { + case 2: /* SZC */ + /* SZC --- Set Zeros Corresponding */ + /* D &= ~S */ + value = readwordX(cpustate, dest, dst_map) & (~ value); + setst_lae(cpustate, value); + writewordX(cpustate, dest, value, dst_map); + CYCLES(2, 14, 4); + break; + case 3: /* S */ + /* S ----- Subtract */ + /* D -= S */ + value = setst_sub_laeco(cpustate, readwordX(cpustate, dest, dst_map), value); + writewordX(cpustate, dest, value, dst_map); + CYCLES(2, 14, 4); + break; + case 4: /* C */ + /* C ----- Compare */ + /* ST = (D - S) */ + setst_c_lae(cpustate, readwordX(cpustate, dest, dst_map), value); + CYCLES(5, 14, 4); + break; + case 5: /* A */ + /* A ----- Add */ + /* D += S */ + value = setst_add_laeco(cpustate, readwordX(cpustate, dest, dst_map), value); + writewordX(cpustate, dest, value, dst_map); + CYCLES(2, 14, 4); + break; + case 6: /* MOV */ + /* MOV --- MOVe */ + /* D = S */ + setst_lae(cpustate, value); + #if ((TMS99XX_MODEL >= TMS9900_ID) && (TMS99XX_MODEL <= TMS9985_ID)) + /* MOV performs a dummy read with tms9900/9980 (but neither ti990/10 nor tms9995) */ + (void)readwordX(cpustate, dest, dst_map); + #endif + writewordX(cpustate, dest, value, dst_map); + CYCLES(1, 14, 3); + break; + case 7: /* SOC */ + /* SOC --- Set Ones Corresponding */ + /* D |= S */ + value = value | readwordX(cpustate, dest, dst_map); + setst_lae(cpustate, value); + writewordX(cpustate, dest, value, dst_map); + CYCLES(2, 14, 4); + break; + } +} + +/* byte instruction */ +static void h4000b(tms99xx_state *cpustate, UINT16 opcode) +{ + register UINT16 src; + register UINT16 dest; + register UINT16 value; + +#if HAS_MAPPING + int src_map = (opcode & 0x0030) ? cpustate->cur_src_map : cpustate->cur_map; + int dst_map = (opcode & 0x0c00) ? cpustate->cur_dst_map : cpustate->cur_map; +#endif + + src = decipheraddrbyte(cpustate, opcode); + dest = decipheraddrbyte(cpustate, opcode >> 6); + + value = readbyteX(cpustate, src, src_map); + + switch ((opcode >> 13) & 0x0007) /* ((opcode & 0xE000) >> 13) */ + { + case 2: /* SZCB */ + /* SZCB -- Set Zeros Corresponding, Byte */ + /* D &= ~S */ + value = readbyteX(cpustate, dest, dst_map) & (~ value); + setst_byte_laep(cpustate, value); + writebyteX(cpustate, dest, value, dst_map); + CYCLES(3, 14, 4); + break; + case 3: /* SB */ + /* SB ---- Subtract, Byte */ + /* D -= S */ + value = setst_subbyte_laecop(cpustate, readbyteX(cpustate, dest, dst_map), value); + writebyteX(cpustate, dest, value, dst_map); + CYCLES(3, 14, 4); + break; + case 4: /* CB */ + /* CB ---- Compare Bytes */ + /* ST = (D - S) */ + setst_c_lae(cpustate, readbyteX(cpustate, dest, dst_map)<<8, value<<8); + cpustate->lastparity = value; + CYCLES(5, 14, 4); + break; + case 5: /* AB */ + /* AB ---- Add, Byte */ + /* D += S */ + value = setst_addbyte_laecop(cpustate, readbyteX(cpustate, dest, dst_map), value); + writebyteX(cpustate, dest, value, dst_map); + CYCLES(3, 14, 4); + break; + case 6: /* MOVB */ + /* MOVB -- MOVe Bytes */ + /* D = S */ + setst_byte_laep(cpustate, value); + #if (TMS99XX_MODEL <= TMS9985_ID) + /* On ti990/10 and tms9900, MOVB needs to read destination, because it cannot actually + read one single byte. It reads a word, replaces the relevant byte, then write + the result. A tms9980 should not need to do so, but still does, because it is just + a tms9900 with a 16 to 8 bit multiplexer (instead of a new chip design, like tms9995). */ + (void)readbyteX(cpustate, dest, dst_map); + #endif + writebyteX(cpustate, dest, value, dst_map); + CYCLES(3, 14, 3); + break; + case 7: /* SOCB */ + /* SOCB -- Set Ones Corresponding, Byte */ + /* D |= S */ + value = value | readbyteX(cpustate, dest, dst_map); + setst_byte_laep(cpustate, value); + writebyteX(cpustate, dest, value, dst_map); + CYCLES(3, 14, 4); + break; + } +} + + +INLINE void execute(tms99xx_state *cpustate, UINT16 opcode) +{ +#if (! HAS_9995_OPCODES) + + /* tms9900-like instruction set*/ + + static void (*const jumptable_short[128])(tms99xx_state *,UINT16) = + { + &illegal,&h0200,&h0400,&h0400,&h0800,&h0800,&illegal,&illegal, + &h1000,&h1000,&h1000,&h1000,&h1000,&h1000,&h1000,&h1000, + &h2000,&h2000,&h2000,&h2000,&h2000,&h2000,&xop,&xop, + &ldcr_stcr,&ldcr_stcr,&ldcr_stcr,&ldcr_stcr,&h2000,&h2000,&h2000,&h2000, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b + }; + + (* jumptable_short[opcode >> 9])(cpustate, opcode); + +#else + + /* tms9989 and tms9995 include 4 extra instructions, and one additionnal instruction type */ + /* tms99000 includes yet another additional instruction */ + + static void (*const jumptable_long[256])(tms99xx_state *,UINT16) = + { + &h0040,&h0100,&h0200,&h0200,&h0400,&h0400,&h0400,&h0400, + &h0800,&h0800,&h0800,&h0800,&illegal,&illegal,&illegal,&illegal, + &h1000,&h1000,&h1000,&h1000,&h1000,&h1000,&h1000,&h1000, + &h1000,&h1000,&h1000,&h1000,&h1000,&h1000,&h1000,&h1000, + &h2000,&h2000,&h2000,&h2000,&h2000,&h2000,&h2000,&h2000, + &h2000,&h2000,&h2000,&h2000,&xop,&xop,&xop,&xop, + &ldcr_stcr,&ldcr_stcr,&ldcr_stcr,&ldcr_stcr,&ldcr_stcr,&ldcr_stcr,&ldcr_stcr,&ldcr_stcr, + &h2000,&h2000,&h2000,&h2000,&h2000,&h2000,&h2000,&h2000, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w,&h4000w, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b, + &h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b,&h4000b + }; + + (* jumptable_long[opcode >> 8])(cpustate, opcode); + +#endif +} + +/************************************************************************** + * Generic set_info + **************************************************************************/ + +static CPU_SET_INFO( tms99xx ) +{ + tms99xx_state *cpustate = get_safe_token(device); + switch (state) + { + /* --- the following bits of info are set as 64-bit signed integers --- */ + case CPUINFO_INT_INPUT_STATE + INPUT_LINE_NMI: tms99xx_set_irq_line(cpustate, INPUT_LINE_NMI, info->i); break; + case CPUINFO_INT_INPUT_STATE + 0: tms99xx_set_irq_line(cpustate, 0, info->i); break; + case CPUINFO_INT_INPUT_STATE + 1: tms99xx_set_irq_line(cpustate, 1, info->i); break; + case CPUINFO_INT_INPUT_STATE + 2: tms99xx_set_irq_line(cpustate, 2, info->i); break; + +#if (TMS99XX_MODEL == TI990_10_ID) + case CPUINFO_INT_PC: + { + const unsigned top = (cpustate->cur_map == 0) ? 0xf800 : 0x10000; + + if ((cpustate->cur_map == 0) && (info->i >= 0x1ff800)) + /* intercept TPCS and CPU ROM */ + cpustate->PC = info->i - 0x1f0000; + else if (! cpustate->mapping_on) + cpustate->PC = (info->i < top) ? info->i : 0; + else + { + if ((info->i >= cpustate->map_files[cpustate->cur_map].bias[0]) + && (info->i <= (cpustate->map_files[cpustate->cur_map].bias[0]+cpustate->map_files[cpustate->cur_map].limit[0]))) + cpustate->PC = info->i - cpustate->map_files[cpustate->cur_map].bias[0]; + else if ((info->i > (cpustate->map_files[cpustate->cur_map].bias[1]+cpustate->map_files[cpustate->cur_map].limit[0])) + && (info->i <= (cpustate->map_files[cpustate->cur_map].bias[1]+cpustate->map_files[cpustate->cur_map].limit[1]))) + cpustate->PC = info->i - cpustate->map_files[cpustate->cur_map].bias[1]; + else if ((info->i > (cpustate->map_files[cpustate->cur_map].bias[2]+cpustate->map_files[cpustate->cur_map].limit[0])) + && (info->i > (cpustate->map_files[cpustate->cur_map].bias[2]+cpustate->map_files[cpustate->cur_map].limit[1])) + && (info->i <= (cpustate->map_files[cpustate->cur_map].bias[2]+cpustate->map_files[cpustate->cur_map].limit[2]))) + cpustate->PC = info->i - cpustate->map_files[cpustate->cur_map].bias[2]; + else + { + /*if ((info->i < top) + && (info->i > cpustate->map_files[cpustate->cur_map].limit[0]) + && (info->i > cpustate->map_files[cpustate->cur_map].limit[1]) + && (info->i > cpustate->map_files[cpustate->cur_map].limit[2])) + cpustate->PC = info->i; + else*/ + cpustate->PC = 0; + } + /*if (info->i >= top) + cpustate->PC = 0;*/ + } + cpustate->PC &= 0xfffe; + } + break; +#else + case CPUINFO_INT_PC: +#endif + case CPUINFO_INT_REGISTER + TMS9900_PC: cpustate->PC = info->i & 0xfffe; break; + case CPUINFO_INT_REGISTER + TMS9900_IR: cpustate->IR = info->i; break; + case CPUINFO_INT_SP: + case CPUINFO_INT_REGISTER + TMS9900_WP: cpustate->WP = info->i & 0xfffe; break; + case CPUINFO_INT_REGISTER + TMS9900_STATUS: cpustate->STATUS = info->i; getstat(cpustate); break; + case CPUINFO_INT_REGISTER + TMS9900_R0: WRITEREG_DEBUG(cpustate, R0, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R1: WRITEREG_DEBUG(cpustate, R1, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R2: WRITEREG_DEBUG(cpustate, R2, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R3: WRITEREG_DEBUG(cpustate, R3, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R4: WRITEREG_DEBUG(cpustate, R4, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R5: WRITEREG_DEBUG(cpustate, R5, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R6: WRITEREG_DEBUG(cpustate, R6, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R7: WRITEREG_DEBUG(cpustate, R7, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R8: WRITEREG_DEBUG(cpustate, R8, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R9: WRITEREG_DEBUG(cpustate, R9, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R10: WRITEREG_DEBUG(cpustate, R10, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R11: WRITEREG_DEBUG(cpustate, R11, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R12: WRITEREG_DEBUG(cpustate, R12, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R13: WRITEREG_DEBUG(cpustate, R13, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R14: WRITEREG_DEBUG(cpustate, R14, info->i); break; + case CPUINFO_INT_REGISTER + TMS9900_R15: WRITEREG_DEBUG(cpustate, R15, info->i); break; + } +} + + + +/************************************************************************** + * Generic get_info + **************************************************************************/ + +void TMS99XX_GET_INFO(legacy_cpu_device *device, UINT32 state, cpuinfo *info) +{ + tms99xx_state *cpustate = (device != NULL && device->token() != NULL) ? get_safe_token(device) : NULL; + switch (state) + { + /* --- the following bits of info are returned as 64-bit signed integers --- */ + case CPUINFO_INT_CONTEXT_SIZE: info->i = sizeof(tms99xx_state); break; + case CPUINFO_INT_INPUT_LINES: info->i = 3; break; + case CPUINFO_INT_DEFAULT_IRQ_VECTOR: info->i = 0; break; + case CPUINFO_INT_ENDIANNESS: info->i = ENDIANNESS_BIG; break; + case CPUINFO_INT_CLOCK_MULTIPLIER: info->i = 1; break; + case CPUINFO_INT_CLOCK_DIVIDER: info->i = 1; break; + case CPUINFO_INT_MIN_INSTRUCTION_BYTES: info->i = 2; break; + case CPUINFO_INT_MAX_INSTRUCTION_BYTES: info->i = 6;/*8 with 990/12, 99105, 99110*/break; + case CPUINFO_INT_MIN_CYCLES: info->i = 1; break; + case CPUINFO_INT_MAX_CYCLES: info->i = 10;/*TODO: compute this value*/break; + +#if (USE_16_BIT_ACCESSORS) + case CPUINFO_INT_DATABUS_WIDTH + AS_PROGRAM: info->i = 16; break; +#else + case CPUINFO_INT_DATABUS_WIDTH + AS_PROGRAM: info->i = 8; break; +#endif + case CPUINFO_INT_ADDRBUS_WIDTH + AS_PROGRAM: +#if (TMS99XX_MODEL == TI990_10_ID) + /* this CPU has a mapper to expand the address space */ + info->i = 21; +#elif (TMS99XX_MODEL == TMS9900_ID) || (TMS99XX_MODEL == TMS9940_ID) || (TMS99XX_MODEL == TMS9985_ID) || (TMS99XX_MODEL == TMS9995_ID) + /* basic 16-bit address bus */ + info->i = 16; +#elif (TMS99XX_MODEL == TMS9980_ID) + /* 2 address MSBits are unconnected */ + info->i = 14; +#else + #warning "Please check how many address bits your CPU has." + info->i = 16; +#endif + break; + case CPUINFO_INT_ADDRBUS_SHIFT + AS_PROGRAM: info->i = 0; break; + case CPUINFO_INT_DATABUS_WIDTH + AS_DATA: info->i = 0; break; + case CPUINFO_INT_ADDRBUS_WIDTH + AS_DATA: info->i = 0; break; + case CPUINFO_INT_ADDRBUS_SHIFT + AS_DATA: info->i = 0; break; + case CPUINFO_INT_DATABUS_WIDTH + AS_IO: info->i = 8; break; + case CPUINFO_INT_ADDRBUS_WIDTH + AS_IO: +#if (TMS99XX_MODEL == TI990_10_ID) + /* 3 MSBs do exist, although they are not connected (don't ask...) */ + info->i = 15; +#elif (TMS99XX_MODEL == TMS9900_ID) + /* 3 MSBs are external instruction code */ + info->i = 12; +#elif (TMS99XX_MODEL == TMS9980_ID) + /* 2 bits unused, and 2 MSBs are external instruction code */ + info->i = 13; +#elif (TMS99XX_MODEL == TMS9940_ID) + /* 9 internal address lines (8 external) */ + info->i = 9; +#elif (TMS99XX_MODEL == TMS9995_ID) + /* 15-bit address and data bus lines D0-D2 provide the external instruction code */ + info->i = 15+3; +#else + #warning "I don't know how your processor handles CRU." + info->i = 15; +#endif + break; + case CPUINFO_INT_ADDRBUS_SHIFT + AS_IO: info->i = 0; break; + +/* not implemented */ +/* case CPUINFO_INT_INPUT_STATE + INPUT_LINE_NMI: info->i = get_irq_line(INPUT_LINE_NMI); break; + case CPUINFO_INT_INPUT_STATE + 0: info->i = get_irq_line(0); break; + case CPUINFO_INT_INPUT_STATE + 1: info->i = get_irq_line(1); break; + case CPUINFO_INT_INPUT_STATE + 2: info->i = get_irq_line(2); break;*/ + + case CPUINFO_INT_PREVIOUSPC: /* not implemented */ break; + +#if (TMS99XX_MODEL == TI990_10_ID) + case CPUINFO_INT_PC: + if ((cpustate->cur_map == 0) && (cpustate->PC >= 0xf800)) + /* intercept TPCS and CPU ROM */ + info->i = 0x1f0000+cpustate->PC; + else if (! cpustate->mapping_on) + info->i = cpustate->PC; + else + { + int map_index; + + if (cpustate->PC <= cpustate->map_files[cpustate->cur_map].limit[0]) + map_index = 0; + else if (cpustate->PC <= cpustate->map_files[cpustate->cur_map].limit[1]) + map_index = 1; + else if (cpustate->PC <= cpustate->map_files[cpustate->cur_map].limit[2]) + map_index = 2; + else + { + info->i = cpustate->PC; + break; + } + + info->i = cpustate->map_files[cpustate->cur_map].bias[map_index]+cpustate->PC; + } + break; +#else + case CPUINFO_INT_PC: +#endif + case CPUINFO_INT_REGISTER + TMS9900_PC: info->i = cpustate->PC; break; + case CPUINFO_INT_REGISTER + TMS9900_IR: info->i = cpustate->IR; break; + case CPUINFO_INT_SP: + case CPUINFO_INT_REGISTER + TMS9900_WP: info->i = cpustate->WP; break; + case CPUINFO_INT_REGISTER + TMS9900_STATUS: setstat(cpustate); info->i = cpustate->STATUS; break; + case CPUINFO_INT_REGISTER + TMS9900_R0: info->i = READREG_DEBUG(cpustate, R0); break; + case CPUINFO_INT_REGISTER + TMS9900_R1: info->i = READREG_DEBUG(cpustate, R1); break; + case CPUINFO_INT_REGISTER + TMS9900_R2: info->i = READREG_DEBUG(cpustate, R2); break; + case CPUINFO_INT_REGISTER + TMS9900_R3: info->i = READREG_DEBUG(cpustate, R3); break; + case CPUINFO_INT_REGISTER + TMS9900_R4: info->i = READREG_DEBUG(cpustate, R4); break; + case CPUINFO_INT_REGISTER + TMS9900_R5: info->i = READREG_DEBUG(cpustate, R5); break; + case CPUINFO_INT_REGISTER + TMS9900_R6: info->i = READREG_DEBUG(cpustate, R6); break; + case CPUINFO_INT_REGISTER + TMS9900_R7: info->i = READREG_DEBUG(cpustate, R7); break; + case CPUINFO_INT_REGISTER + TMS9900_R8: info->i = READREG_DEBUG(cpustate, R8); break; + case CPUINFO_INT_REGISTER + TMS9900_R9: info->i = READREG_DEBUG(cpustate, R9); break; + case CPUINFO_INT_REGISTER + TMS9900_R10: info->i = READREG_DEBUG(cpustate, R10); break; + case CPUINFO_INT_REGISTER + TMS9900_R11: info->i = READREG_DEBUG(cpustate, R11); break; + case CPUINFO_INT_REGISTER + TMS9900_R12: info->i = READREG_DEBUG(cpustate, R12); break; + case CPUINFO_INT_REGISTER + TMS9900_R13: info->i = READREG_DEBUG(cpustate, R13); break; + case CPUINFO_INT_REGISTER + TMS9900_R14: info->i = READREG_DEBUG(cpustate, R14); break; + case CPUINFO_INT_REGISTER + TMS9900_R15: info->i = READREG_DEBUG(cpustate, R15); break; + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case CPUINFO_FCT_SET_INFO: info->setinfo = CPU_SET_INFO_NAME(tms99xx); break; + case CPUINFO_FCT_INIT: info->init = CPU_INIT_NAME(tms99xx); break; + case CPUINFO_FCT_RESET: info->reset = CPU_RESET_NAME(tms99xx); break; + case CPUINFO_FCT_EXIT: info->exit = CPU_EXIT_NAME(tms99xx); break; + case CPUINFO_FCT_EXECUTE: info->execute = CPU_EXECUTE_NAME(tms99xx); break; + case CPUINFO_FCT_BURN: info->burn = NULL; break; + case CPUINFO_FCT_DISASSEMBLE: info->disassemble = CPU_DISASSEMBLE_NAME(tms99xx); break; + case CPUINFO_PTR_INSTRUCTION_COUNTER: info->icount = &cpustate->icount; break; + + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case CPUINFO_STR_NAME: strcpy(info->s, TMS99XX_device_get_name); break; + case CPUINFO_STR_SHORTNAME: strcpy(info->s, TMS99XX_device_get_shortname); break; + + case CPUINFO_STR_FAMILY: strcpy(info->s, "Texas Instruments 9900L"); break; + case CPUINFO_STR_VERSION: strcpy(info->s, "2.0"); break; + case CPUINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; + case CPUINFO_STR_CREDITS: strcpy(info->s, "C TMS9900 emulator by Edward Swartz, initially converted for Mame by M.Coates, updated by R. Nabet"); break; + + case CPUINFO_STR_FLAGS: + sprintf(info->s, "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", + cpustate->WP & 0x8000 ? 'L':'.', + cpustate->WP & 0x4000 ? 'A':'.', + cpustate->WP & 0x2000 ? 'E':'.', + cpustate->WP & 0x1000 ? 'C':'.', + cpustate->WP & 0x0800 ? 'V':'.', + cpustate->WP & 0x0400 ? 'P':'.', + cpustate->WP & 0x0200 ? 'X':'.', + cpustate->WP & 0x0100 ? '?':'.', + cpustate->WP & 0x0080 ? '?':'.', + cpustate->WP & 0x0040 ? '?':'.', + cpustate->WP & 0x0020 ? '?':'.', + cpustate->WP & 0x0010 ? '?':'.', + cpustate->WP & 0x0008 ? 'I':'.', + cpustate->WP & 0x0004 ? 'I':'.', + cpustate->WP & 0x0002 ? 'I':'.', + cpustate->WP & 0x0001 ? 'I':'.'); + break; + + case CPUINFO_STR_REGISTER + TMS9900_PC: sprintf(info->s, "PC :%04X", cpustate->PC); break; + case CPUINFO_STR_REGISTER + TMS9900_IR: sprintf(info->s, "IR :%04X", cpustate->IR); break; + case CPUINFO_STR_REGISTER + TMS9900_WP: sprintf(info->s, "WP :%04X", cpustate->WP); break; + case CPUINFO_STR_REGISTER + TMS9900_STATUS: sprintf(info->s, "ST :%04X", cpustate->STATUS); break; + + case CPUINFO_STR_REGISTER + TMS9900_R0: sprintf(info->s, "R0 :%04X", READREG_DEBUG(cpustate, R0)); break; + case CPUINFO_STR_REGISTER + TMS9900_R1: sprintf(info->s, "R1 :%04X", READREG_DEBUG(cpustate, R1)); break; + case CPUINFO_STR_REGISTER + TMS9900_R2: sprintf(info->s, "R2 :%04X", READREG_DEBUG(cpustate, R2)); break; + case CPUINFO_STR_REGISTER + TMS9900_R3: sprintf(info->s, "R3 :%04X", READREG_DEBUG(cpustate, R3)); break; + case CPUINFO_STR_REGISTER + TMS9900_R4: sprintf(info->s, "R4 :%04X", READREG_DEBUG(cpustate, R4)); break; + case CPUINFO_STR_REGISTER + TMS9900_R5: sprintf(info->s, "R5 :%04X", READREG_DEBUG(cpustate, R5)); break; + case CPUINFO_STR_REGISTER + TMS9900_R6: sprintf(info->s, "R6 :%04X", READREG_DEBUG(cpustate, R6)); break; + case CPUINFO_STR_REGISTER + TMS9900_R7: sprintf(info->s, "R7 :%04X", READREG_DEBUG(cpustate, R7)); break; + case CPUINFO_STR_REGISTER + TMS9900_R8: sprintf(info->s, "R8 :%04X", READREG_DEBUG(cpustate, R8)); break; + case CPUINFO_STR_REGISTER + TMS9900_R9: sprintf(info->s, "R9 :%04X", READREG_DEBUG(cpustate, R9)); break; + case CPUINFO_STR_REGISTER + TMS9900_R10: sprintf(info->s, "R10:%04X", READREG_DEBUG(cpustate, R10)); break; + case CPUINFO_STR_REGISTER + TMS9900_R11: sprintf(info->s, "R11:%04X", READREG_DEBUG(cpustate, R11)); break; + case CPUINFO_STR_REGISTER + TMS9900_R12: sprintf(info->s, "R12:%04X", READREG_DEBUG(cpustate, R12)); break; + case CPUINFO_STR_REGISTER + TMS9900_R13: sprintf(info->s, "R13:%04X", READREG_DEBUG(cpustate, R13)); break; + case CPUINFO_STR_REGISTER + TMS9900_R14: sprintf(info->s, "R14:%04X", READREG_DEBUG(cpustate, R14)); break; + case CPUINFO_STR_REGISTER + TMS9900_R15: sprintf(info->s, "R15:%04X", READREG_DEBUG(cpustate, R15)); break; + } +} diff --git a/src/devices/cpu/tms9900/ti990_10.c b/src/devices/cpu/tms9900/ti990_10.c new file mode 100644 index 00000000000..b6cd8d32d41 --- /dev/null +++ b/src/devices/cpu/tms9900/ti990_10.c @@ -0,0 +1,148 @@ +// license:BSD-3-Clause +// copyright-holders:Michael Zapf + +/* + Texas Instruments TI990/10 CPU board + + The first member of the family was actually the ti990/10 minicomputer, + released in 1975. tms9900 was released in 1976, and has the same + instruction set as ti990/10: however, tms9900 is slower, it does not + support privileges and memory mapping, and illegal instructions do not + cause an error interrupt. + + The ti990 family later evoluted into the huge ti990/12 system, with support + for 144 different instructions, and microcode programming in case some user + found it was not enough. ti990/10 was eventually replaced by a cheaper + ti990/10a board, built around a tms99000 microprocessor. + + tms99000 is the successor to both ti9900 and ti990/10. It supports + privileges, and has a coprocessor interface which enables the use of an + external memory mapper. Additionnally, it can use a Macrostore ROM to + emulate additional instructions. + + **** This is WORK IN PROGRESS **** +*/ + +#include "ti990_10.h" + +/* + The following defines can be set to 0 or 1 to disable or enable certain + output in the log. +*/ +// Emulation setup +#define TRACE_SETUP 0 + +// Emulation details +#define TRACE_EMU 0 + +/**************************************************************************** + Constructor for TI 990/10 + The CRU mask is related to the bits, not to their addresses which are + twice their number. Accordingly, the TMS9900 has a CRU bitmask 0x0fff. +****************************************************************************/ + +ti990_10_device::ti990_10_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : cpu_device(mconfig, TI990_10, "TI990/10 CPU", tag, owner, clock, "ti990_10_cpu", __FILE__), + m_program_config("program", ENDIANNESS_BIG, 16, 16), + m_io_config("cru", ENDIANNESS_BIG, 8, 12), + m_prgspace(NULL), + m_cru(NULL) +{ +} + +ti990_10_device::~ti990_10_device() +{ +} + +void ti990_10_device::device_start() +{ + m_prgspace = &space(AS_PROGRAM); + m_cru = &space(AS_IO); + + // set our instruction counter + m_icountptr = &m_icount; + + state_add(STATE_GENPC, "curpc", PC).formatstr("%4s").noshow(); + state_add(STATE_GENFLAGS, "status", m_state_any).callimport().callexport().formatstr("%16s").noshow(); +} + +void ti990_10_device::device_stop() +{ + if (TRACE_SETUP) logerror("ti990_10: Deleting lookup tables\n"); +} + +/* + TI990_10 hard reset + The device reset is just the emulator's trigger for the reset procedure + which is invoked via the main loop. +*/ +void ti990_10_device::device_reset() +{ + if (TRACE_EMU) logerror("ti990_10: Device reset by emulator\n"); +} + +const address_space_config *ti990_10_device::memory_space_config(address_spacenum spacenum) const +{ + switch (spacenum) + { + case AS_PROGRAM: + return &m_program_config; + + case AS_IO: + return &m_io_config; + + default: + return NULL; + } +} + +void ti990_10_device::execute_run() +{ + do + { + // TODO: Complete the implementation + m_icount--; + } while (m_icount>0); +} + +void ti990_10_device::execute_set_input(int irqline, int state) +{ +} + +// ========================================================================== + +UINT32 ti990_10_device::execute_min_cycles() const +{ + return 2; +} + +// TODO: Compute this value, just a wild guess for the average +UINT32 ti990_10_device::execute_max_cycles() const +{ + return 10; +} + +UINT32 ti990_10_device::execute_input_lines() const +{ + return 2; +} + +// device_disasm_interface overrides +UINT32 ti990_10_device::disasm_min_opcode_bytes() const +{ + return 2; +} + +UINT32 ti990_10_device::disasm_max_opcode_bytes() const +{ + return 6; +} + +// TODO: check 9900dasm +offs_t ti990_10_device::disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options) +{ + extern CPU_DISASSEMBLE( tms9900 ); + return CPU_DISASSEMBLE_NAME(tms9900)(this, buffer, pc, oprom, opram, options); +} + +const device_type TI990_10 = &device_creator<ti990_10_device>; diff --git a/src/devices/cpu/tms9900/ti990_10.h b/src/devices/cpu/tms9900/ti990_10.h new file mode 100644 index 00000000000..f1d4e20ce3a --- /dev/null +++ b/src/devices/cpu/tms9900/ti990_10.h @@ -0,0 +1,61 @@ +// license:BSD-3-Clause +// copyright-holders:Michael Zapf +/* + TI 990 CPU board + See ti990_10.c for documentation +*/ + +#ifndef __TI990_10_H__ +#define __TI990_10_H__ + +#include "emu.h" +#include "debugger.h" +#include "tms99com.h" + +class ti990_10_device : public cpu_device +{ +public: + ti990_10_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + ~ti990_10_device(); + +protected: + // device-level overrides + void device_start(); + void device_stop(); + void device_reset(); + + // device_execute_interface overrides + UINT32 execute_min_cycles() const; + UINT32 execute_max_cycles() const; + UINT32 execute_input_lines() const; + void execute_set_input(int irqline, int state); + void execute_run(); + + // device_disasm_interface overrides + UINT32 disasm_min_opcode_bytes() const; + UINT32 disasm_max_opcode_bytes() const; + offs_t disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options); + + const address_space_config* memory_space_config(address_spacenum spacenum) const; + + const address_space_config m_program_config; + const address_space_config m_io_config; + address_space* m_prgspace; + address_space* m_cru; + + // Cycle counter + int m_icount; + + // Hardware registers + UINT16 WP; // Workspace pointer + UINT16 PC; // Program counter + UINT16 ST; // Status register + +private: + UINT16 m_state_any; +}; + +// device type definition +extern const device_type TI990_10; + +#endif /* __TI990_10_H__ */ diff --git a/src/devices/cpu/tms9900/tms9900.c b/src/devices/cpu/tms9900/tms9900.c new file mode 100644 index 00000000000..0029d9cf392 --- /dev/null +++ b/src/devices/cpu/tms9900/tms9900.c @@ -0,0 +1,2736 @@ +// license:BSD-3-Clause +// copyright-holders:Michael Zapf +/* + Texas Instruments TMS9900 + + +--------------------+ + V_BB | 1 o 64| /HOLD + V_CC | 2 63| /MEMEN + WAIT | 3 62| READY + /LOAD | 4 61| /WE + HOLDA | 5 60| CRUCLK + /RESET | 6 59| V_CC + IAQ | 7 58| - + PHI1 | 8 57| - + PHI2 | 9 56| D15 -+ LSB + LSB +- A14 |10 55| D14 | + | A13 |11 54| D13 | + | A12 |12 53| D12 | + | A11 |13 52| D11 | + Address | A10 |14 +--------+ 51| D10 | Data + bus | A9 |15 | | 50| D9 | bus + 32K * | A8 |16 | | 49| D8 | 16 bit + 16bit | A7 |17 | | 48| D7 | + | A6 |18 | | 47| D6 | + | A5 |19 +--------+ 46| D5 | + | A4 |20 45| D4 | + | A3 |21 44| D3 | + | A2 |22 43| D2 | + | A1 |23 42| D1 | + MSB +- A0 |24 41| D0 -+ MSB + PHI4 |25 40| V_SS + V_SS |26 39| - + V_DD |27 38| - + PHI3 |28 37| - + DBIN |29 36| IC0 -+ MSB + CRUOUT |30 35| IC1 | Interrupt + CRUIN |31 34| IC2 | level + /INTREQ |32 33| IC3 -+ LSB + +--------------------+ + + WAIT out Processor in wait state + /LOAD in Non-maskable interrupt + HOLDA out Hold acknowledge + /RESET in Reset + IAQ out Instruction acquisition + PHI1-4 in Clock phase inputs + DBIN out Data bus in input mode + CRUOUT out Communication register unit data output + CRUIN in Communication register unit data input + /INTREQ in Interrupt request + CRUCLK out Communication register unit clock output + /WE out Data available for memory write + READY in Memory ready for access + /MEMEN out Address bus contains memory address + /HOLD in External device acquires address and data bus lines + + V_BB -5V supply + V_CC +5V supply (pins 2 and 59 connected in parallel) + V_DD +12V supply + V_SS 0V Ground reference (pins 26 and 40 connected in parallel) + + A0-A14 out Address bus (32768 words of 16 bit width) + D0-A15 i/o Data bus + IC0-IC3 in Interrupt level (0-15) + + Note that Texas Instruments' bit numberings define bit 0 as the + most significant bit (different to most other systems). Also, the + system uses big-endian memory organisation: Storing the word 0x1234 at + address 0x0000 means that the byte 0x12 is stored at 0x0000 and byte 0x34 + is stored at 0x0001. + + The processor also knows byte-oriented operations (like add byte (AB), + move byte (MOVB)). This makes it necessary for the CPU to read the word + from the target memory location first, change the respective byte, and + write it back. + + See the TI-99/4A driver for an application of the TMS9900 processor + within an 8-bit data bus board layout (using a data bus multiplexer). + + Subcycle handling + + In this implementation we try to emulate the internal operations as + precisely as possible, following the technical specifications. We need + not try to be clock-precise with every tick; it suffices to perform + the proper number of operations within a given time span. + + For each command the CPU executes a microprogram which requires some + amount of cycles to complete. During this time the external clock continues + to issue pulses which can be used to control wait state creation. As we + do not emulate external clocks this implementation offers an extra output + "clock_out" (which, however, is available for the TMS9995) which pulses + at a rate of 3 MHz. External devices (e.g. memory controllers) may count + the pulses and pull down the READY line (with set_ready) as needed. + + Another possibility for creating wait states is to pull down the line + for some time set by a timer. This is done, for example, by circuits like + GROMs or speech synthesis processors (TMS52xx). + + TODO: + - Fine-tune cycles + - State save + - HOLD state should be tested; I don't have test cases yet + + Michael Zapf, June 2012 +*/ + +#include "tms9900.h" + +/* tms9900 ST register bits. */ +enum +{ + ST_LH = 0x8000, // Logical higher (unsigned comparison) + ST_AGT = 0x4000, // Arithmetical greater than (signed comparison) + ST_EQ = 0x2000, // Equal + ST_C = 0x1000, // Carry + ST_OV = 0x0800, // Overflow (when using signed operations) + ST_OP = 0x0400, // Odd parity (used with byte operations) + ST_X = 0x0200, // XOP + ST_IM = 0x000f // Interrupt mask +}; + +/* + The following defines can be set to 0 or 1 to disable or enable certain + output in the log. +*/ +// Emulation setup +#define TRACE_SETUP 0 + +// Emulation details +#define TRACE_EMU 0 + +// Location and command +#define TRACE_EXEC 0 + +// Memory operation +#define TRACE_MEM 0 + +// Address bus operation +#define TRACE_ADDRESSBUS 0 + +// Cycle count +#define TRACE_CYCLES 0 + +// Clock ticks +#define TRACE_CLOCK 0 + +// Wait states +#define TRACE_WAIT 0 + +// Interrupts +#define TRACE_INT 0 + +// CRU operation +#define TRACE_CRU 0 + +// Status register +#define TRACE_STATUS 0 + +// ALU details +#define TRACE_ALU 0 + +// Microinstruction level +#define TRACE_MICRO 0 + +/**************************************************************************** + Common constructor for TMS9900 and TMS9980A + The CRU mask is related to the bits, not to their addresses which are + twice their number. Accordingly, the TMS9900 has a CRU bitmask 0x0fff. +****************************************************************************/ + +tms99xx_device::tms99xx_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, int databus_width, int prg_addr_bits, int cru_addr_bits, device_t *owner, UINT32 clock, const char *shortname, const char *source) + : cpu_device(mconfig, type, name, tag, owner, clock, shortname, source), + m_program_config("program", ENDIANNESS_BIG, databus_width, prg_addr_bits), + m_io_config("cru", ENDIANNESS_BIG, 8, cru_addr_bits), + m_prgspace(NULL), + m_cru(NULL), + m_prgaddr_mask((1<<prg_addr_bits)-1), + m_cruaddr_mask((1<<cru_addr_bits)-1), + m_clock_out_line(*this), + m_wait_line(*this), + m_holda_line(*this), + m_iaq_line(*this), + m_get_intlevel(*this), + m_dbin_line(*this), + m_external_operation(*this) +{ +} + +tms99xx_device::~tms99xx_device() +{ +} + +/**************************************************************************** + Constructor for TMS9900 +****************************************************************************/ + +tms9900_device::tms9900_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : tms99xx_device(mconfig, TMS9900, "TMS9900", tag, 16, 16, 12, owner, clock, "tms9900", __FILE__) +{ +} + +enum +{ + TMS9900_PC=0, TMS9900_WP, TMS9900_STATUS, TMS9900_IR, + TMS9900_R0, TMS9900_R1, TMS9900_R2, TMS9900_R3, + TMS9900_R4, TMS9900_R5, TMS9900_R6, TMS9900_R7, + TMS9900_R8, TMS9900_R9, TMS9900_R10, TMS9900_R11, + TMS9900_R12, TMS9900_R13, TMS9900_R14, TMS9900_R15 +}; + +void tms99xx_device::device_start() +{ + // TODO: Restore state save feature + resolve_lines(); + m_prgspace = &space(AS_PROGRAM); + m_cru = &space(AS_IO); + + // set our instruction counter + m_icountptr = &m_icount; + + m_state_any = 0; + PC = 0; + m_hold_state = false; + + // add the states for the debugger + for (int i=0; i < 20; i++) + { + // callimport = need to use the state_import method to write to the state variable + // callexport = need to use the state_export method to read the state variable + state_add(i, s_statename[i], m_state_any).callimport().callexport().formatstr("%04X"); + } + state_add(STATE_GENPC, "curpc", PC).formatstr("%4s").noshow(); + state_add(STATE_GENFLAGS, "status", m_state_any).callimport().callexport().formatstr("%16s").noshow(); + + build_command_lookup_table(); + + m_program = NULL; +} + +void tms99xx_device::device_stop() +{ + int k = 0; + if (TRACE_SETUP) logerror("tms99xx: Deleting lookup tables\n"); + while (m_lotables[k]!=NULL) delete[] m_lotables[k++]; +} + +/* + External connections +*/ +void tms99xx_device::resolve_lines() +{ + // Resolve our external connections + m_external_operation.resolve(); + m_get_intlevel.resolve(); + m_iaq_line.resolve(); + m_clock_out_line.resolve(); + m_wait_line.resolve(); + m_holda_line.resolve(); + m_dbin_line.resolve(); // we need this for the set_address operation +} + +/* + TMS9900 hard reset + The device reset is just the emulator's trigger for the reset procedure + which is invoked via the main loop. +*/ +void tms99xx_device::device_reset() +{ + if (TRACE_EMU) logerror("tms99xx: Device reset by emulator\n"); + m_reset = true; + m_check_ready = false; + m_wait_state = false; + ST = 0; + m_irq_state = false; +} + +const char* tms99xx_device::s_statename[20] = +{ + "PC", "WP", "ST", "IR", + "R0", "R1", "R2", "R3", + "R4", "R5", "R6", "R7", + "R8", "R9", "R10","R11", + "R12","R13","R14","R15" +}; + +/* + Write the contents of a register by external input (debugger) +*/ +void tms99xx_device::state_import(const device_state_entry &entry) +{ + int index = entry.index(); + switch (entry.index()) + { + case STATE_GENFLAGS: + // no action here; we do not allow import, as the flags are all + // bits of the STATUS register + break; + case TMS9900_PC: + PC = (UINT16)(m_state_any & m_prgaddr_mask & 0xfffe); + break; + case TMS9900_WP: + WP = (UINT16)(m_state_any & m_prgaddr_mask & 0xfffe); + break; + case TMS9900_STATUS: + ST = (UINT16)m_state_any; + break; + case TMS9900_IR: + IR = (UINT16)m_state_any; + break; + default: + // Workspace registers + if (index <= TMS9900_R15) + write_workspace_register_debug(index-TMS9900_R0, (UINT16)m_state_any); + break; + } +} + +/* + Reads the contents of a register for display in the debugger. +*/ +void tms99xx_device::state_export(const device_state_entry &entry) +{ + int index = entry.index(); + switch (entry.index()) + { + case STATE_GENFLAGS: + m_state_any = ST; + break; + case TMS9900_PC: + m_state_any = PC; + break; + case TMS9900_WP: + m_state_any = WP; + break; + case TMS9900_STATUS: + m_state_any = ST; + break; + case TMS9900_IR: + m_state_any = IR; + break; + default: + // Workspace registers + if (index <= TMS9900_R15) + m_state_any = read_workspace_register_debug(index-TMS9900_R0); + break; + } +} + +/* + state_string_export - export state as a string for the debugger +*/ +void tms99xx_device::state_string_export(const device_state_entry &entry, std::string &str) +{ + static const char *statestr = "LAECOPX-----IIII"; + char flags[17]; + memset(flags, 0x00, ARRAY_LENGTH(flags)); + UINT16 val = 0x8000; + if (entry.index()==STATE_GENFLAGS) + { + for (int i=0; i < 16; i++) + { + flags[i] = ((val & ST)!=0)? statestr[i] : '.'; + val = (val >> 1) & 0x7fff; + } + } + str.assign(flags); +} + +/**************************************************************************/ + +UINT16 tms99xx_device::read_workspace_register_debug(int reg) +{ + int temp = m_icount; + m_prgspace->set_debugger_access(true); + UINT16 value = m_prgspace->read_word((WP+(reg<<1)) & m_prgaddr_mask & 0xfffe); + m_prgspace->set_debugger_access(false); + m_icount = temp; + return value; +} + +void tms99xx_device::write_workspace_register_debug(int reg, UINT16 data) +{ + int temp = m_icount; + m_prgspace->set_debugger_access(true); + m_prgspace->write_word((WP+(reg<<1)) & m_prgaddr_mask & 0xfffe, data); + m_prgspace->set_debugger_access(false); + m_icount = temp; +} + +const address_space_config *tms99xx_device::memory_space_config(address_spacenum spacenum) const +{ + switch (spacenum) + { + case AS_PROGRAM: + return &m_program_config; + + case AS_IO: + return &m_io_config; + + default: + return NULL; + } +} + +/************************************************************************** + Microprograms for the CPU instructions + + The actions which are specific to the respective instruction are + invoked by repeated calls of ALU_xxx; each call increases a state + variable so that on the next call, the next part can be processed. + This saves us a lot of additional functions. +**************************************************************************/ + +/* + Define the indices for the micro-operation table. This is done for the sake + of a simpler microprogram definition as an UINT8[]. +*/ +enum +{ + IAQ = 0, + MEMORY_READ, + MEMORY_WRITE, + REG_READ, + REG_WRITE, + CRU_INPUT, + CRU_OUTPUT, + DATA_DERIVE, + RET, + ABORT, + END, + + ALU_NOP, + ALU_CLR, + ALU_SETADDR, + ALU_ADDONE, + ALU_SETADDR_ADDONE, + ALU_PCADDR_ADVANCE, + ALU_SOURCE, + ALU_ADDREG, + ALU_IMM, + ALU_REG, + ALU_F1, + ALU_COMP, + ALU_F3, + ALU_MPY, + ALU_DIV, + ALU_XOP, + ALU_CLR_SWPB, + ALU_ABS, + ALU_X, + ALU_B, + ALU_BLWP, + ALU_LDCR, + ALU_STCR, + ALU_SBZ_SBO, + ALU_TB, + ALU_JMP, + ALU_SHIFT, + ALU_AI_ORI, + ALU_CI, + ALU_LI, + ALU_LWPI, + ALU_LIMI, + ALU_STWP_STST, + ALU_EXT, + ALU_RTWP, + ALU_INT +}; + + +#define MICROPROGRAM(_MP) \ + static const UINT8 _MP[] = + +/* + This is a kind of subroutine with 6 variants. Might be done in countless + better ways, but will suffice for now. Each variant has at most 8 steps + RET will return to the caller. + The padding simplifies the calculation of the start address: We just + take the Ts field as an index. In the last two cases we add an offset of 8 + if we have an indexed (resp. a byte) operation. +*/ +MICROPROGRAM(data_derivation) +{ + REG_READ, RET, 0, 0, 0, 0, 0, 0, // Rx (00) + 0, 0, 0, 0, 0, 0, 0, 0, + REG_READ, ALU_SETADDR, MEMORY_READ, RET, 0, 0, 0, 0, // *Rx (01) + 0, 0, 0, 0, 0, 0, 0, 0, + ALU_CLR, ALU_PCADDR_ADVANCE, MEMORY_READ, ALU_ADDREG, MEMORY_READ, RET, 0, 0, // @sym (10) + REG_READ, ALU_PCADDR_ADVANCE, MEMORY_READ, ALU_ADDREG, MEMORY_READ, RET, 0, 0, // @sym(Rx) (10) + REG_READ, ALU_SETADDR_ADDONE, ALU_ADDONE, REG_WRITE, MEMORY_READ, RET, 0, 0, // *Rx+ (word) (11) + REG_READ, ALU_SETADDR_ADDONE, REG_WRITE, MEMORY_READ, RET, 0, 0, 0 // *Rx+ (byte) (11) +}; + +MICROPROGRAM(f1_mp) +{ + ALU_NOP, + DATA_DERIVE, + ALU_SOURCE, // Store the word + DATA_DERIVE, + ALU_F1, + MEMORY_WRITE, + END +}; + +MICROPROGRAM(comp_mp) +{ + ALU_NOP, + DATA_DERIVE, + ALU_SOURCE, + DATA_DERIVE, + ALU_COMP, + ALU_NOP, // Compare operations do not write back any data + END +}; + +MICROPROGRAM(f3_mp) +{ + ALU_NOP, + DATA_DERIVE, + ALU_F3, + MEMORY_READ, // We have to distinguish this from the C/CB microprogram above + ALU_F3, + ALU_NOP, // Compare operations do not write back any data + END +}; + +MICROPROGRAM(xor_mp) +{ + ALU_NOP, + DATA_DERIVE, + ALU_F3, + MEMORY_READ, + ALU_F3, + MEMORY_WRITE, // XOR again must write back data, cannot reuse f3_mp + END +}; + +MICROPROGRAM(mult_mp) +{ + ALU_NOP, + DATA_DERIVE, + ALU_MPY, // Save the value; put register number in m_regnumber + MEMORY_READ, + ALU_MPY, // 18 cycles for multiplication + MEMORY_WRITE, // Write the high word + ALU_MPY, // Get low word, increase m_address + MEMORY_WRITE, + END +}; + +MICROPROGRAM(div_mp) +{ + ALU_NOP, + DATA_DERIVE, // Get divisor + ALU_DIV, // 0 Store divisor and get register number + MEMORY_READ, // Read register + ALU_DIV, // 1 Check overflow, increase address (or abort here) + ABORT, + MEMORY_READ, // Read subsequent word (if reg=15 this is behind the workspace) + ALU_DIV, // 2 Calculate quotient (takes variable amount of cycles; at least 32 machine cycles), set register number + MEMORY_WRITE, // Write quotient into register + ALU_DIV, // 3 Get remainder + MEMORY_WRITE, // Write remainder + END +}; + +MICROPROGRAM(xop_mp) +{ + ALU_NOP, + DATA_DERIVE, // Get argument + ALU_XOP, // 0 Save the address of the source operand, set address = 0x0040 + xopNr*4, 6 cycles + MEMORY_READ, // Read the new WP + ALU_XOP, // 1 Save old WP, set new WP, get the source operand address + MEMORY_WRITE, // Write the address of the source operand into the new R11 + ALU_XOP, // 2 + MEMORY_WRITE, // Write the ST into the new R15 + ALU_XOP, // 3 + MEMORY_WRITE, // Write the PC into the new R14 + ALU_XOP, // 4 + MEMORY_WRITE, // Write the WP into the new R13 + ALU_XOP, // 5 Set the X bit in the ST + MEMORY_READ, // Read the new PC + ALU_XOP, // 6 Set the new PC + END +}; + +MICROPROGRAM(clr_swpb_mp) +{ + ALU_NOP, + DATA_DERIVE, + ALU_CLR_SWPB, + MEMORY_WRITE, + END +}; + +MICROPROGRAM(abs_mp) +{ + ALU_NOP, + DATA_DERIVE, + ALU_ABS, // two cycles + MEMORY_WRITE, // skipped when ABS is not performed + ALU_NOP, + END +}; + +MICROPROGRAM(x_mp) +{ + ALU_NOP, + DATA_DERIVE, + ALU_X, + END +}; + +MICROPROGRAM(b_mp) // Branch +{ + ALU_NOP, + DATA_DERIVE, + ALU_B, + END +}; + +MICROPROGRAM(bl_mp) // Branch and Link +{ + ALU_NOP, + DATA_DERIVE, + ALU_B, + ALU_NOP, + MEMORY_WRITE, + END +}; + +MICROPROGRAM(blwp_mp) // Branch and Load WP +{ + ALU_NOP, + DATA_DERIVE, // Get argument + ALU_BLWP, // 0 Save old WP, set new WP, save position + ALU_NOP, + MEMORY_WRITE, // write ST to R15 + ALU_BLWP, // 1 + MEMORY_WRITE, // write PC to R14 + ALU_BLWP, // 2 + MEMORY_WRITE, // write WP to R13 + ALU_BLWP, // 3 Get saved position + MEMORY_READ, // Read new PC + ALU_BLWP, // 4 Set new PC + END +}; + +MICROPROGRAM(ldcr_mp) +{ + ALU_NOP, + DATA_DERIVE, + ALU_SOURCE, + ALU_NOP, + ALU_LDCR, + ALU_NOP, + MEMORY_READ, + ALU_LDCR, + CRU_OUTPUT, + ALU_NOP, + END +}; + +MICROPROGRAM(stcr_mp) +{ + ALU_NOP, + DATA_DERIVE, + ALU_SOURCE, // Store address and value + ALU_STCR, // 0 Set register_number = 12; 0 cycles (already done before) + MEMORY_READ, + ALU_STCR, // 1 Prepare CRU access + ALU_NOP, + CRU_INPUT, + ALU_STCR, // 2 Create result; Cycles = 5 + (8-#C-1) or + (16-#C) + ALU_NOP, + ALU_NOP, + ALU_NOP, + MEMORY_WRITE, + END +}; + +MICROPROGRAM(sbz_sbo_mp) +{ + ALU_SBZ_SBO, + ALU_NOP, + MEMORY_READ, + ALU_SBZ_SBO, + CRU_OUTPUT, + END +}; + +MICROPROGRAM(tb_mp) +{ + ALU_TB, + MEMORY_READ, + ALU_TB, + CRU_INPUT, + ALU_TB, + END +}; + +MICROPROGRAM(jmp_mp) +{ + ALU_NOP, + ALU_JMP, + ALU_JMP, + ALU_NOP, + END +}; + +MICROPROGRAM(shift_mp) +{ + ALU_SHIFT, + MEMORY_READ, + ALU_SHIFT, // 2 cycles if count != 0, else 4 + MEMORY_READ, // skipped if count != 0 + ALU_SHIFT, // skipped if count != 0 (4 cycles) + ALU_SHIFT, + MEMORY_WRITE, + ALU_NOP, + END +}; + +MICROPROGRAM(ai_ori_mp) +{ + ALU_REG, + MEMORY_READ, + ALU_IMM, + MEMORY_READ, + ALU_AI_ORI, + MEMORY_WRITE, + END +}; + +MICROPROGRAM(ci_mp) +{ + ALU_REG, + MEMORY_READ, + ALU_IMM, + MEMORY_READ, + ALU_CI, + ALU_NOP, + END +}; + +MICROPROGRAM(li_mp) +{ + ALU_IMM, + MEMORY_READ, + ALU_LI, // sets status bits + ALU_REG, // set register number + MEMORY_WRITE, + END +}; + +MICROPROGRAM(lwpi_mp) +{ + ALU_IMM, + MEMORY_READ, + ALU_NOP, + ALU_LWPI, // sets WP + END +}; + +MICROPROGRAM(limi_mp) +{ + ALU_IMM, + MEMORY_READ, + ALU_NOP, + ALU_LIMI, // sets interrupt mask in ST + ALU_NOP, + ALU_NOP, + END +}; + +MICROPROGRAM(stwp_stst_mp) +{ + ALU_STWP_STST, + ALU_REG, + MEMORY_WRITE, + END +}; + +MICROPROGRAM(external_mp) +{ + ALU_NOP, + ALU_NOP, + ALU_EXT, + ALU_NOP, + ALU_NOP, + END +}; + +MICROPROGRAM(rtwp_mp) +{ + ALU_NOP, + ALU_RTWP, + MEMORY_READ, + ALU_RTWP, // no cycles + MEMORY_READ, + ALU_RTWP, // no cycles + MEMORY_READ, + ALU_RTWP, + END +}; + +MICROPROGRAM(int_mp) +{ + ALU_NOP, + ALU_INT, // 0 Set address = 0 + MEMORY_READ, + ALU_INT, // 1 Save old WP, set new WP, save position + MEMORY_WRITE, // write ST to R15 + ALU_INT, // 2 + MEMORY_WRITE, // write PC to R14 + ALU_INT, // 3 + MEMORY_WRITE, // write WP to R13 + ALU_INT, // 4 Get saved position + MEMORY_READ, // Read new PC + ALU_INT, // 5 Set new PC + END +}; + +const tms99xx_device::ophandler tms99xx_device::s_microoperation[] = +{ + &tms99xx_device::acquire_instruction, + &tms99xx_device::mem_read, + &tms99xx_device::mem_write, + &tms99xx_device::register_read, + &tms99xx_device::register_write, + &tms99xx_device::cru_input_operation, + &tms99xx_device::cru_output_operation, + &tms99xx_device::data_derivation_subprogram, + &tms99xx_device::return_from_subprogram, + &tms99xx_device::abort_operation, + &tms99xx_device::command_completed, + + &tms99xx_device::alu_nop, + &tms99xx_device::alu_clear, + &tms99xx_device::alu_setaddr, + &tms99xx_device::alu_addone, + &tms99xx_device::alu_setaddr_addone, + &tms99xx_device::alu_pcaddr_advance, + &tms99xx_device::alu_source, + &tms99xx_device::alu_add_register, + &tms99xx_device::alu_imm, + &tms99xx_device::alu_reg, + + &tms99xx_device::alu_f1, + &tms99xx_device::alu_comp, + &tms99xx_device::alu_f3, + &tms99xx_device::alu_multiply, + &tms99xx_device::alu_divide, + &tms99xx_device::alu_xop, + &tms99xx_device::alu_clr_swpb, + &tms99xx_device::alu_abs, + &tms99xx_device::alu_x, + &tms99xx_device::alu_b, + &tms99xx_device::alu_blwp, + &tms99xx_device::alu_ldcr, + &tms99xx_device::alu_stcr, + &tms99xx_device::alu_sbz_sbo, + &tms99xx_device::alu_tb, + &tms99xx_device::alu_jmp, + &tms99xx_device::alu_shift, + &tms99xx_device::alu_ai_ori, + &tms99xx_device::alu_ci, + &tms99xx_device::alu_li, + &tms99xx_device::alu_lwpi, + &tms99xx_device::alu_limi, + &tms99xx_device::alu_stwp_stst, + &tms99xx_device::alu_external, + &tms99xx_device::alu_rtwp, + &tms99xx_device::alu_int +}; + +/***************************************************************************** + CPU instructions +*****************************************************************************/ + +/* + Available instructions +*/ +enum +{ + ILL=0, A, AB, ABS, AI, ANDI, B, BL, BLWP, C, + CB, CI, CKOF, CKON, CLR, COC, CZC, DEC, DECT, DIV, + IDLE, INC, INCT, INV, JEQ, JGT, JH, JHE, JL, JLE, + JLT, JMP, JNC, JNE, JNO, JOC, JOP, LDCR, LI, LIMI, + LREX, LWPI, MOV, MOVB, MPY, NEG, ORI, RSET, RTWP, S, + SB, SBO, SBZ, SETO, SLA, SOC, SOCB, SRA, SRC, SRL, + STCR, STST, STWP, SWPB, SZC, SZCB, TB, X, XOP, XOR, + INTR +}; + +/* + Formats: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + ----+------------------------------------------------+ + 1 | Opcode | B | Td | RegNr | Ts | RegNr | + +--------+---+----+------------+----+------------+ + 2 | Opcode | Displacement | + +-----------------------+------------------------+ + 3 | Opcode | RegNr | Ts | RegNr | + +-----------------+------------+----+------------+ + 4 | Opcode | Count | Ts | RegNr | + +-----------------+------------+----+------------+ + 5 | Opcode | Count | RegNr | + +-----------------------+-----------+------------+ + 6 | Opcode | Ts | RegNr | + +------------------------------+----+------------+ + 7 | Opcode |0| 0| 0| 0| 0 | + +---------------------------------+-+--+--+--+---+ + 8 | Opcode |0| RegNr | + +---------------------------------+-+------------+ + 9 | Opcode | Reg/Nr | Ts | RegNr | + +-----------------+------------+----+------------+ +*/ + +/* + Defines the number of bits from the left which are significant for the + command in the respective format. +*/ +static const int format_mask_len[] = +{ + 0, 4, 8, 6, 6, 8, 10, 16, 12, 6 +}; + +const tms99xx_device::tms_instruction tms99xx_device::s_command[] = +{ + // Opcode, ID, format, microprg + { 0x0200, LI, 8, li_mp }, + { 0x0220, AI, 8, ai_ori_mp }, + { 0x0240, ANDI, 8, ai_ori_mp }, + { 0x0260, ORI, 8, ai_ori_mp }, + { 0x0280, CI, 8, ci_mp }, + { 0x02a0, STWP, 8, stwp_stst_mp }, + { 0x02c0, STST, 8, stwp_stst_mp }, + { 0x02e0, LWPI, 8, lwpi_mp }, + { 0x0300, LIMI, 8, limi_mp }, + { 0x0340, IDLE, 7, external_mp }, + { 0x0360, RSET, 7, external_mp }, + { 0x0380, RTWP, 7, rtwp_mp }, + { 0x03a0, CKON, 7, external_mp }, + { 0x03c0, CKOF, 7, external_mp }, + { 0x03e0, LREX, 7, external_mp }, + { 0x0400, BLWP, 6, blwp_mp }, + { 0x0440, B, 6, b_mp }, + { 0x0480, X, 6, x_mp }, + { 0x04c0, CLR, 6, clr_swpb_mp }, + { 0x0500, NEG, 6, clr_swpb_mp }, + { 0x0540, INV, 6, clr_swpb_mp }, + { 0x0580, INC, 6, clr_swpb_mp }, + { 0x05c0, INCT, 6, clr_swpb_mp }, + { 0x0600, DEC, 6, clr_swpb_mp }, + { 0x0640, DECT, 6, clr_swpb_mp }, + { 0x0680, BL, 6, bl_mp }, + { 0x06c0, SWPB, 6, clr_swpb_mp }, + { 0x0700, SETO, 6, clr_swpb_mp }, + { 0x0740, ABS, 6, abs_mp }, + { 0x0800, SRA, 5, shift_mp }, + { 0x0900, SRL, 5, shift_mp }, + { 0x0a00, SLA, 5, shift_mp }, + { 0x0b00, SRC, 5, shift_mp }, + { 0x1000, JMP, 2, jmp_mp }, + { 0x1100, JLT, 2, jmp_mp }, + { 0x1200, JLE, 2, jmp_mp }, + { 0x1300, JEQ, 2, jmp_mp }, + { 0x1400, JHE, 2, jmp_mp }, + { 0x1500, JGT, 2, jmp_mp }, + { 0x1600, JNE, 2, jmp_mp }, + { 0x1700, JNC, 2, jmp_mp }, + { 0x1800, JOC, 2, jmp_mp }, + { 0x1900, JNO, 2, jmp_mp }, + { 0x1a00, JL, 2, jmp_mp }, + { 0x1b00, JH, 2, jmp_mp }, + { 0x1c00, JOP, 2, jmp_mp }, + { 0x1d00, SBO, 2, sbz_sbo_mp }, + { 0x1e00, SBZ, 2, sbz_sbo_mp }, + { 0x1f00, TB, 2, tb_mp }, + { 0x2000, COC, 3, f3_mp }, + { 0x2400, CZC, 3, f3_mp }, + { 0x2800, XOR, 3, xor_mp }, + { 0x2c00, XOP, 3, xop_mp }, + { 0x3000, LDCR, 4, ldcr_mp }, + { 0x3400, STCR, 4, stcr_mp }, + { 0x3800, MPY, 9, mult_mp }, + { 0x3c00, DIV, 9, div_mp }, + { 0x4000, SZC, 1, f1_mp }, + { 0x5000, SZCB, 1, f1_mp }, + { 0x6000, S, 1, f1_mp }, + { 0x7000, SB, 1, f1_mp }, + { 0x8000, C, 1, comp_mp }, + { 0x9000, CB, 1, comp_mp }, + { 0xa000, A, 1, f1_mp }, + { 0xb000, AB, 1, f1_mp }, + { 0xc000, MOV, 1, f1_mp }, + { 0xd000, MOVB, 1, f1_mp }, + { 0xe000, SOC, 1, f1_mp }, + { 0xf000, SOCB, 1, f1_mp } +}; + +/* + Create a B-tree for looking up the commands. Each node can carry up to + 16 entries, indexed by 4 consecutive bits in the opcode. + + Works as follows: + + Opcode = 0201 (Load immediate value into register 1) + Opcode = 0284 (Compare immediate value with register 4) + + Table: [ Table0, table1, table2, ... tableF ] + | + +-------+ + v + table0: [ table00, table01, table02, ... table0f ] + | + +-------------------------+ + v + table02: [ table020, table021, ... table028, ... table02f ] + | | | + v v v + Entry NULL Entry + for LI for CI + + For each level in the tree, four more bits are compared. The search + terminates when the number of compared bits is equal or higher than + the number of significant bits of the format of this opcode. The entry + points to the respective line in s_command. + + This way we can decode all format 1 commands by a single pass (including the + most frequent command MOV), and almost all commands by less than four passes. + + The disadvantage is that we have to build these tables from the opcode + list at runtime, and many positions are empty. But we do not need more + than 20 tables for the TMS command set. +*/ +void tms99xx_device::build_command_lookup_table() +{ + int i = 0; + int cmdindex = 0; + int bitcount; + const tms_instruction *inst; + UINT16 opcode; + int k = 0; + + m_command_lookup_table = new lookup_entry[16]; + // We use lotables as a list of allocated tables - to be able to delete them + // at the end. + m_lotables[k++] = m_command_lookup_table; + + lookup_entry* table = m_command_lookup_table; + for (int j=0; j < 16; j++) + { + table[j].entry = NULL; + table[j].next_digit = NULL; + } + + do + { + inst = &s_command[i]; + table = m_command_lookup_table; + if (TRACE_SETUP) logerror("tms99xx: === opcode=%04x, len=%d\n", inst->opcode, format_mask_len[inst->format]); + bitcount = 4; + opcode = inst->opcode; + cmdindex = (opcode>>12) & 0x000f; + + while (bitcount < format_mask_len[inst->format]) + { + // Descend + if (table[cmdindex].next_digit == NULL) + { + if (TRACE_SETUP) logerror("tms99xx: create new table at bitcount=%d for index=%d\n", bitcount, cmdindex); + table[cmdindex].next_digit = new lookup_entry[16]; + m_lotables[k++] = table[cmdindex].next_digit; + for (int j=0; j < 16; j++) + { + table[cmdindex].next_digit[j].next_digit = NULL; + table[cmdindex].next_digit[j].entry = NULL; + } + } + else + { + if (TRACE_SETUP) logerror("tms99xx: found a table at bitcount=%d\n", bitcount); + } + + table = table[cmdindex].next_digit; + + bitcount = bitcount+4; + opcode <<= 4; + cmdindex = (opcode>>12) & 0x000f; + if (TRACE_SETUP) logerror("tms99xx: next index=%x\n", cmdindex); + } + + if (TRACE_SETUP) logerror("tms99xx: bitcount=%d\n", bitcount); + // We are at the target level + // Need to fill in the same entry for all values in the bitcount + // (if a command needs 10 bits we have to copy it four + // times for all combinations with 12 bits) + for (int j=0; j < (1<<(bitcount-format_mask_len[inst->format])); j++) + { + if (TRACE_SETUP) logerror("tms99xx: opcode=%04x at position %d\n", inst->opcode, cmdindex+j); + table[cmdindex+j].entry = inst; + } + + i++; + } while (inst->opcode != 0xf000); + + m_lotables[k++] = NULL; + if (TRACE_SETUP) logerror("tms99xx: Allocated %d tables\n", k); +} + +/* + Main execution loop + + For each invocation of execute_run, a number of loop iterations has been + calculated before (m_icount). Each loop iteration is one clock cycle. + The loop must be executed for the number of times that corresponds to the + time until the next timer event. + + In this implementation, each loop iteration also causes the clock line to + pulse once. External devices may use this pulse to decrement counters + which control the READY line. + + Machine cycles to clock input: + + +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ + | | | | | | | | | | | | | | | | | | clock (1 of 4 phases) + +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ + + |-------|-------|-------|-------|---- cycles (2 clock pulses each) + + Wait states only have effect for memory operations. They are processed as + follows: + + 1) The CPU sets the address bus for reading. If READY is low, the CPU + waits for the next clock tick repeatedly until READY is high again. + When this is the case, the data bus is sampled on the next clock tick + and the read operation is complete. + + As we do not have a split-phase read operation in this emulation + we actually read the data bus instantly but wait for the READY line to + be high again. + + 2) The CPU sets the address bus for writing. In the same moment, the data + bus is loaded with the word to be written. On the next clock tick, + the CPU checks the READY line and waits until it is high. When READY + is high at a clock tick, the operation is complete on the next clock tick. +*/ +void tms99xx_device::execute_run() +{ + if (m_reset) service_interrupt(); + + if (TRACE_EMU) logerror("tms99xx: calling execute_run for %d cycles\n", m_icount); + do + { + // Only when last instruction has completed + if (m_program == NULL) + { + if (m_load_state) + { + logerror("tms99xx: LOAD interrupt\n"); + m_irq_level = LOAD_INT; + m_irq_state = false; + service_interrupt(); + } + else + { + // Interrupts are serviced when + // - an interrupt condition is signaled over INTREQ and + // - the level indicated by IC0-IC3 is lower than the interrupt mask value and + // - the previous instruction is not an XOP or BLWP + if (m_irq_state && (m_irq_level <= (ST & 0x000f)) && (m_command != XOP && m_command != BLWP)) + service_interrupt(); + } + } + + if (m_program == NULL && m_idle_state) + { + if (TRACE_WAIT) logerror("tms99xx: idle state\n"); + pulse_clock(1); + if (!m_external_operation.isnull()) + { + m_external_operation(IDLE_OP, 0, 0xff); + m_external_operation(IDLE_OP, 1, 0xff); + } + } + else + { + // Handle HOLD + // A HOLD request is signalled through the input line HOLD. + // The hold state will be entered with the next non-memory access cycle. + if (m_hold_state && + (m_program==NULL || + (m_program[MPC] != IAQ && + m_program[MPC] != MEMORY_READ && m_program[MPC] != MEMORY_WRITE && + m_program[MPC] != REG_READ && m_program[MPC] != REG_WRITE))) + { + if (TRACE_WAIT) logerror("tms99xx: hold\n"); + if (!m_hold_acknowledged) acknowledge_hold(); + pulse_clock(1); + } + else + { + // Normal operation + if (m_check_ready && m_ready == false) + { + // We are in a wait state + set_wait_state(true); + if (TRACE_WAIT) logerror("tms99xx: wait\n"); + // The clock output should be used to change the state of an outer + // device which operates the READY line + pulse_clock(1); + } + else + { + set_wait_state(false); + m_check_ready = false; + + if (m_program==NULL) m_op = IAQ; + else + { + m_op = m_program[MPC]; + } + if (TRACE_MICRO) logerror("tms99xx: MPC = %d, m_op = %d\n", MPC, m_op); + // Call the operation of the microprogram + (this->*s_microoperation[m_op])(); + // If we have multiple passes (as in the TMS9980) + m_pass--; + if (m_pass<=0) + { + m_pass = 1; + MPC++; + m_mem_phase = 1; + if (!m_iaq_line.isnull()) m_iaq_line(CLEAR_LINE); + } + } + } + } + } while (m_icount>0 && !m_reset); + if (TRACE_EMU) logerror("tms99xx: cycles expired; will return soon.\n"); +} + +/**************************************************************************/ + +/* + Interrupt input +*/ +void tms99xx_device::execute_set_input(int irqline, int state) +{ + if (irqline==INT_9900_RESET && state==ASSERT_LINE) + { + m_reset = true; + } + else + { + if (irqline == INT_9900_LOAD) + { + m_load_state = (state==ASSERT_LINE); + m_irq_level = -1; + m_reset = false; + } + else + { + m_irq_state = (state==ASSERT_LINE); + if (state==ASSERT_LINE) + { + m_irq_level = get_intlevel(state); + if (TRACE_INT) logerror("tms99xx: /INT asserted, level=%d, ST=%04x\n", m_irq_level, ST); + } + else + { + if (TRACE_INT) logerror("tms99xx: /INT cleared\n"); + } + } + } +} + +/* + This can be overloaded by variants of TMS99xx. +*/ +int tms99xx_device::get_intlevel(int state) +{ + if (!m_get_intlevel.isnull()) return m_get_intlevel(0); + return 0; +} + +void tms99xx_device::service_interrupt() +{ + m_program = int_mp; + m_command = INTR; + m_idle_state = false; + if (!m_external_operation.isnull()) m_external_operation(IDLE_OP, 0, 0xff); + + m_state = 0; + + if (!m_dbin_line.isnull()) m_dbin_line(ASSERT_LINE); + + // If reset, we just start with execution, otherwise we put the MPC + // on the first microinstruction, which also means that the main loop shall + // leave it where it is. So we pretend we have another pass to do. + m_pass = m_reset? 1 : 2; + + if (m_reset) + { + m_irq_level = RESET_INT; + + m_ready_bufd = true; + m_ready = true; + m_load_state = false; + m_hold_state = false; + m_hold_acknowledged = false; + m_wait_state = false; + IR = 0; + ST = 0; + m_mem_phase = 1; + + m_reset = false; + } + if (TRACE_INT) + { + switch (m_irq_level) + { + case RESET_INT: logerror("tms99xx: **** triggered a RESET interrupt\n"); break; + case LOAD_INT: logerror("tms99xx: **** triggered a LOAD (NMI) interrupt\n"); break; + default: logerror("tms99xx: ** triggered an interrupt on level %d\n", m_irq_level); break; + } + } + + MPC = 0; + m_first_cycle = m_icount; +} + +/* + Issue a pulse on the clock line. +*/ +void tms99xx_device::pulse_clock(int count) +{ + for (int i=0; i < count; i++) + { + if (!m_clock_out_line.isnull()) m_clock_out_line(ASSERT_LINE); + m_ready = m_ready_bufd; // get the latched READY state + if (!m_clock_out_line.isnull()) m_clock_out_line(CLEAR_LINE); + m_icount--; // This is the only location where we count down the cycles. + if (TRACE_CLOCK) + { + if (m_check_ready) logerror("tms99xx: pulse_clock, READY=%d\n", m_ready? 1:0); + else logerror("tms99xx: pulse_clock\n"); + } + } +} + +/* + Enter the hold state. +*/ +void tms99xx_device::set_hold(int state) +{ + m_hold_state = (state==ASSERT_LINE); + if (!m_hold_state) + { + m_hold_acknowledged = false; + if (!m_holda_line.isnull()) m_holda_line(CLEAR_LINE); + } +} + +/* + Acknowledge the HOLD request. +*/ +inline void tms99xx_device::acknowledge_hold() +{ + m_hold_acknowledged = true; + if (!m_holda_line.isnull()) m_holda_line(ASSERT_LINE); +} + +/* + Signal READY to the CPU. When cleared, the CPU enters wait states. This + becomes effective on a clock pulse. +*/ +void tms99xx_device::set_ready(int state) +{ + m_ready_bufd = (state==ASSERT_LINE); +} + +void tms99xx_device::abort_operation() +{ + command_completed(); +} + +/* + Enter or leave the wait state. We only operate the WAIT line when there is a change. +*/ +inline void tms99xx_device::set_wait_state(bool state) +{ + if (m_wait_state != state) + if (!m_wait_line.isnull()) m_wait_line(state? ASSERT_LINE : CLEAR_LINE); + m_wait_state = state; +} + +/* + Acquire the next word as an instruction. The program counter advances by + one word. +*/ +void tms99xx_device::decode(UINT16 inst) +{ + int index = 0; + lookup_entry* table = m_command_lookup_table; + UINT16 opcode = inst; + bool complete = false; + const tms_instruction *decoded; + + m_state = 0; + IR = inst; + m_get_destination = false; + m_byteop = false; + + while (!complete) + { + index = (opcode >> 12) & 0x000f; + if (TRACE_MICRO) logerror("tms99xx: Check next hex digit of instruction %x\n", index); + if (table[index].next_digit != NULL) + { + table = table[index].next_digit; + opcode = opcode << 4; + } + else complete = true; + } + decoded = table[index].entry; + if (decoded == NULL) + { + // not found + logerror("tms99xx: Illegal opcode %04x\n", inst); + IR = 0; + // This will cause another instruction acquisition in the next machine cycle + // with an asserted IAQ line (can be used to indicate this illegal opcode detection). + m_program = NULL; + } + else + { + m_program = decoded->prog; + MPC = -1; + m_command = decoded->id; + if (TRACE_MICRO) logerror("tms99xx: Command decoded as id %d, %s, base opcode %04x\n", m_command, opname[m_command], decoded->opcode); + // Byte operations are either format 1 with the byte flag set + // or format 4 (CRU multi bit operations) with 1-8 bits to transfer. + m_byteop = ((decoded->format==1 && ((IR & 0x1000)!=0)) + || (decoded->format==4 && (((IR >> 6)&0x000f) > 0) && (((IR >> 6)&0x000f) > 9))); + } + m_pass = 1; +} + +inline bool tms99xx_device::byte_operation() +{ + return (IR & 0x1000)!=0; +} + +void tms99xx_device::acquire_instruction() +{ + if (m_mem_phase == 1) + { + if (!m_iaq_line.isnull()) m_iaq_line(ASSERT_LINE); + m_address = PC; + m_first_cycle = m_icount; + } + + mem_read(); + + if (m_mem_phase == 1) + { + decode(m_current_value); + if (TRACE_EXEC) logerror("tms99xx: %04x: %04x (%s)\n", PC, IR, opname[m_command]); + debugger_instruction_hook(this, PC); + PC = (PC + 2) & 0xfffe & m_prgaddr_mask; + // IAQ will be cleared in the main loop + } +} + +/* + Memory read + Clock cycles: 2 + W, W = number of wait states +*/ +void tms99xx_device::mem_read() +{ + // After set_address, any device attached to the address bus may pull down + // READY in order to put the CPU into wait state before the read_word + // operation will be performed + // set_address and read_word should pass the same address as argument + if (m_mem_phase==1) + { + if (!m_dbin_line.isnull()) m_dbin_line(ASSERT_LINE); + m_prgspace->set_address(m_address & m_prgaddr_mask & 0xfffe); + m_check_ready = true; + m_mem_phase = 2; + m_pass = 2; + if (TRACE_ADDRESSBUS) logerror("tms99xx: set address (r) %04x\n", m_address); + + pulse_clock(1); // Concludes the first cycle + // If READY has been found to be low, the CPU will now stay in the wait state loop + } + else + { + // Second phase (after READY was raised again) + m_current_value = m_prgspace->read_word(m_address & m_prgaddr_mask & 0xfffe); + pulse_clock(1); + if (!m_dbin_line.isnull()) m_dbin_line(CLEAR_LINE); + m_mem_phase = 1; // reset to phase 1 + if (TRACE_MEM) logerror("tms99xx: mem r %04x -> %04x\n", m_address, m_current_value); + } +} + +void tms99xx_device::mem_write() +{ + if (m_mem_phase==1) + { + if (!m_dbin_line.isnull()) m_dbin_line(CLEAR_LINE); + // When writing, the data bus is asserted immediately after the address bus + if (TRACE_ADDRESSBUS) logerror("tms99xx: set address (w) %04x\n", m_address); + m_prgspace->set_address(m_address & m_prgaddr_mask & 0xfffe); + if (TRACE_MEM) logerror("tms99xx: mem w %04x <- %04x\n", m_address, m_current_value); + m_prgspace->write_word(m_address & m_prgaddr_mask & 0xfffe, m_current_value); + m_check_ready = true; + m_mem_phase = 2; + m_pass = 2; + pulse_clock(1); + } + else + { + // Second phase (we arrive here when the wait states are over) + pulse_clock(1); + } +} + +void tms99xx_device::register_read() +{ + // Need to set m_address for F1/F3 (we don't know what the data_derive did) + if (m_mem_phase==1) + { + m_address = WP + (m_regnumber<<1); + } + + mem_read(); + + if (m_mem_phase==1) + { + m_register_contents = m_current_value; + } +} + +/* + Memory write: + + Clock cycles: 2 + W, W = number of wait states +*/ +void tms99xx_device::register_write() +{ + // This will be called twice; m_pass is set by the embedded mem_write + UINT16 addr_save = m_address; + m_address = (WP + (m_regnumber<<1)) & m_prgaddr_mask & 0xfffe; + mem_write(); + m_address = addr_save; +} + +/* + CRU support code + + The CRU bus is a 1-bit-wide I/O bus. The CPU can read or write bits at random address. + Special instructions are dedicated to reading and writing one or several consecutive bits. + + The CRU uses the same address bus as the normal memory access. For writing, + the CRUCLK line is pulsed, but not for reading where CRUCLK stays cleared. + This means that each normal memory access also causes read accesses on the + CRU side. The /MEMEN line may be used to distinguish the kinds of accesses + as it stays cleared during CRU operations. + + We do not emulate this here as it seems there are no real applications of + this side effect. Real designs must ensure that CRU read operations are + idempotent (i.e. they must not change the state of the queried device). + + Read returns the number of consecutive CRU bits, with increasing CRU address + from the least significant to the most significant bit; right-aligned + + There seems to be no handling of wait states during CRU operations on the + TMS9900. The TMS9995, in contrast, respects wait states during the transmission + of each single bit. + + Usage of this method: + CRU write: First bit is at rightmost position of m_value. +*/ + +void tms99xx_device::cru_input_operation() +{ + int value, value1; + int offset, location; + + location = (m_cru_address >> 4) & (m_cruaddr_mask>>3); + offset = (m_cru_address>>1) & 0x07; + + // Read 8 bits (containing the desired bits) + value = m_cru->read_byte(location); + + if ((offset + m_count) > 8) // spans two 8 bit cluster + { + // Read next 8 bits + location = (location + 1) & (m_cruaddr_mask>>3); + value1 = m_cru->read_byte(location); + value |= (value1 << 8); + + if ((offset + m_count) > 16) // spans three 8 bit cluster + { + // Read next 8 bits + location = (location + 1) & (m_cruaddr_mask>>3); + value1 = m_cru->read_byte(location); + value |= (value1 << 16); + } + } + + // On each machine cycle (2 clocks) only one CRU bit is transmitted + pulse_clock(m_count<<1); + + // Shift back the bits so that the first bit is at the rightmost place + m_value = (value >> offset); + + // Mask out what we want + m_value &= (0x0000ffff >> (16-m_count)); +} + +void tms99xx_device::cru_output_operation() +{ + int value; + int location; + location = (m_cru_address >> 1) & m_cruaddr_mask; + value = m_value; + + // Write m_count bits from cru_address + for (int i=0; i < m_count; i++) + { + if (TRACE_CRU) logerror("tms99xx: CRU output operation, address %04x, value %d\n", location<<1, value & 0x01); + m_cru->write_byte(location, (value & 0x01)); + value >>= 1; + location = (location + 1) & m_cruaddr_mask; + pulse_clock(2); + } +} + +void tms99xx_device::return_from_subprogram() +{ + // Return from data derivation + // The result should be in m_current_value + // and the address in m_address + m_program = m_caller; + MPC = m_caller_MPC; // will be increased on return +} + +void tms99xx_device::command_completed() +{ + // Pseudo state at the end of the current instruction cycle sequence + if (TRACE_CYCLES) + { + logerror("tms99xx: ------"); + int cycles = m_first_cycle - m_icount; + // Avoid nonsense values due to expired and resumed main loop + if (cycles > 0 && cycles < 10000) logerror(" %d cycles", cycles); + logerror("\n"); + } + m_program = NULL; +} + +/* + This is a switch to a subprogram; there is only one, the data + derivation. In terms of cycles, it does not take any time; execution + continues with the first instruction of the subprogram. +*/ +void tms99xx_device::data_derivation_subprogram() +{ + UINT16 ircopy = IR; + + // Save the return program and position + m_caller = m_program; + m_caller_MPC = MPC; + + // Source or destination argument? + if (m_get_destination) ircopy >>= 6; + + m_regnumber = ircopy & 0x000f; + + m_program = (UINT8*)data_derivation; + MPC = ircopy & 0x0030; + + if (((MPC == 0x0020) && (m_regnumber != 0)) // indexed + || ((MPC == 0x0030) && m_byteop)) // byte operation + { + MPC += 8; // the second option + } + m_get_destination = true; // when we call this the second time before END it's the destination + m_pass = 2; +} + + +/************************************************************************** + Status bit operations +**************************************************************************/ + +inline void tms99xx_device::set_status_bit(int bit, bool state) +{ + if (state) ST |= bit; + else ST &= ~bit; +} + +void tms99xx_device::set_status_parity(UINT8 value) +{ + int count = 0; + for (int i=0; i < 8; i++) + { + if ((value & 0x80)!=0) count++; + value <<= 1; + } + set_status_bit(ST_OP, (count & 1)!=0); +} + +inline void tms99xx_device::compare_and_set_lae(UINT16 value1, UINT16 value2) +{ + set_status_bit(ST_EQ, value1 == value2); + set_status_bit(ST_LH, value1 > value2); + set_status_bit(ST_AGT, (INT16)value1 > (INT16)value2); + if (TRACE_STATUS) logerror("tms99xx: ST = %04x (val1=%04x, val2=%04x)\n", ST, value1, value2); +} + +/************************************************************************** + ALU operations +**************************************************************************/ + +void tms99xx_device::alu_nop() +{ + // Do nothing (or nothing that is externally visible) + pulse_clock(2); + return; +} + +void tms99xx_device::alu_source() +{ + // Copy the current value into the source data register + m_source_even = ((m_address & 1)==0); + m_source_value = m_current_value; + m_source_address = m_address; + pulse_clock(2); +} + +void tms99xx_device::alu_clear() +{ + // Clears the register contents + m_register_contents = 0; + pulse_clock(2); +} + +void tms99xx_device::alu_setaddr() +{ + // Load the current value into the address register + m_address = m_current_value; + pulse_clock(2); +} + +void tms99xx_device::alu_addone() +{ + m_current_value++; + pulse_clock(2); +} + +void tms99xx_device::alu_setaddr_addone() +{ + // Set the address register and increase the recent value + m_address = m_current_value; + m_current_value++; + pulse_clock(2); +} + +void tms99xx_device::alu_pcaddr_advance() +{ + // Set PC as new read address, increase by 2 + m_address = PC; + PC = (PC + 2) & 0xfffe & m_prgaddr_mask; + pulse_clock(2); +} + +void tms99xx_device::alu_add_register() +{ + // Add the register contents to the current value and set as address + m_address = m_current_value + m_register_contents; + pulse_clock(2); +} + +void tms99xx_device::alu_imm() +{ + m_value_copy = m_current_value; + m_address_copy = m_address; + m_address = PC; + PC = (PC + 2) & 0xfffe & m_prgaddr_mask; + pulse_clock(2); +} + +void tms99xx_device::alu_reg() +{ + m_address = (WP + ((IR & 0x000f)<<1)) & m_prgaddr_mask; + pulse_clock(2); +} + +void tms99xx_device::alu_f1() +{ + UINT32 dest_new = 0; + + // Save the destination value + UINT16 prev_dest_value = m_current_value; + + m_destination_even = ((m_address & 1)==0); // this is the destination address; the source address has already been saved + bool byteop = byte_operation(); + + if (byteop) + { + if (!m_destination_even) m_current_value <<= 8; + if (!m_source_even) m_source_value <<= 8; + // We have to strip away the low byte, or byte operations may fail + // e.g. 0x10ff + 0x0101 = 0x1200 + // or 0x2000 - 0x0101 = 0x1eff + m_source_value &= 0xff00; + m_current_value &= 0xff00; + } + + switch (m_command) + { + case A: + case AB: + // Add the contents of the source data to the destination data + // May exceed 0xffff (for carry check) + dest_new = m_current_value + m_source_value; + + // 1000 + e000 = f000 (L) + // c000 + c000 = 8000 (LC) + // 7000 + 4000 = b000 (LO) + // 2000 + f000 = 1000 (LAC) + // c000 + b000 = 7000 (LACO) + // 2000 + e000 = 0000 (EC) + // 8000 + 8000 = 0000 (ECO) + + // When adding, a carry occurs when we exceed the 0xffff value. + set_status_bit(ST_C, (dest_new & 0x10000) != 0); + // If the result has a sign bit that is different from both arguments, we have an overflow + // (i.e. getting a negative value from two positive values and vice versa) + set_status_bit(ST_OV, ((dest_new ^ m_current_value) & (dest_new ^ m_source_value) & 0x8000)!=0); + break; + + case S: + case SB: + // Subtract the contents of the source data from the destination data + dest_new = m_current_value + ((~m_source_value) & 0xffff) + 1; + // LAECO(P) + // 8000 - 8000 = 0000 (EC) + // 2000 - 8000 = a000 (LO) + // 8000 - 2000 = 6000 (LACO) + // 2000 - 1000 = 1000 (LAC) + // 1000 - 2000 = f000 (L) + // 1000 - 1000 = 0000 (EC) + // 1000 - f000 = 2000 (LA) + // f000 - 2000 = d000 (LC) + + // Subtraction means adding the 2s complement, so the carry bit + // is set whenever adding the 2s complement exceeds ffff + // In fact the CPU adds the one's complement, then adds a one. This + // explains why subtracting 0 sets the carry bit. + set_status_bit(ST_C, (dest_new & 0x10000) != 0); + + // If the arguments have different sign bits and the result has a + // sign bit different from the destination value, we have an overflow + // e.g. value1 = 0x7fff, value2 = 0xffff; value1-value2 = 0x8000 + // or value1 = 0x8000, value2 = 0x0001; value1-value2 = 0x7fff + // value1 is the destination value + set_status_bit(ST_OV, (m_current_value ^ m_source_value) & (m_current_value ^ dest_new) & 0x8000); + break; + + case SOC: + case SOCB: + // OR the contents of the source data on the destination data + dest_new = m_current_value | m_source_value; + break; + + case SZC: + case SZCB: + // AND the one's complement of the contents of the source data on the destination data + dest_new = m_current_value & ~m_source_value; + break; + + case MOV: + case MOVB: + // Copy the source data to the destination data + dest_new = m_source_value; + break; + } + + if (byteop) + { + set_status_parity((UINT8)(dest_new>>8)); + + // destnew is the new value to be written (high byte); needs to be + // merged with the existing word + if (m_destination_even) + m_current_value = (prev_dest_value & 0x00ff) | (dest_new & 0xff00); + else + m_current_value = (prev_dest_value & 0xff00) | ((dest_new >> 8) & 0x00ff); + compare_and_set_lae((UINT16)(dest_new & 0xff00), 0); + } + else + { + m_current_value = (UINT16)(dest_new & 0xffff); + compare_and_set_lae((UINT16)(dest_new & 0xffff), 0); + } + + pulse_clock(2); +} + +void tms99xx_device::alu_comp() +{ + m_destination_even = ((m_address & 1)==0); // this is the destination address; the source address has already been saved + if (byte_operation()) + { + if (!m_destination_even) m_current_value <<= 8; + if (!m_source_even) m_source_value <<= 8; + set_status_parity((UINT8)(m_source_value>>8)); + compare_and_set_lae(m_source_value & 0xff00, m_current_value & 0xff00); + } + else + compare_and_set_lae(m_source_value, m_current_value); + + pulse_clock(2); +} + +void tms99xx_device::alu_f3() +{ + switch (m_state) + { + case 0: + // Get register address + m_address = WP + ((IR >> 5) & 0x001e); + m_source_value = m_current_value; + break; + case 1: + if (m_command == COC) + { + set_status_bit(ST_EQ, (m_current_value & m_source_value) == m_source_value); + } + else + { + if (m_command == CZC) + { + set_status_bit(ST_EQ, (~m_current_value & m_source_value) == m_source_value); + } + else + { + // XOR + // The workspace register address is still in m_address + m_current_value = (m_current_value ^ m_source_value); + compare_and_set_lae(m_current_value, 0); + } + } + if (TRACE_STATUS) logerror("tms99xx: ST = %04x\n", ST); + break; + } + + m_state++; + pulse_clock(2); +} + +void tms99xx_device::alu_multiply() +{ + UINT32 result = 0; + + switch (m_state) + { + case 0: // After data derivation + m_source_value = m_current_value; + m_address = ((IR >> 5) & 0x001e) + WP; + break; + case 1: // After reading the register (multiplier) + if (TRACE_ALU) logerror("tms99xx: Multiply %04x by %04x\n", m_current_value, m_source_value); + result = (m_source_value & 0x0000ffff) * (m_current_value & 0x0000ffff); + m_current_value = (result >> 16) & 0xffff; + m_value_copy = result & 0xffff; + pulse_clock(34); // add 36 clock cycles (18 machine cycles); last one in main loop + break; + case 2: // After writing the high word to the destination register + m_current_value = m_value_copy; // Prepare to save low word + m_address = (m_address + 2) & m_prgaddr_mask; + break; + } + pulse_clock(2); + m_state++; +} + +void tms99xx_device::alu_divide() +{ + // Format is DIV Divisor,REG(dividend) + UINT32 uval32; + bool overflow = true; + UINT16 value1; + + switch (m_state) + { + case 0: + m_source_value = m_current_value; // store divisor + // Set address of register + m_address = WP + ((IR >> 5) & 0x001e); + m_address_copy = m_address; + break; + case 1: + // We have an overflow when the quotient cannot be stored in 16 bits + // This is the case when the dividend / divisor >= 0x10000, + // or equivalently, dividend / 0x10000 >= divisor + + if (m_current_value < m_source_value) // also if source=0 + { + MPC++; // skip the abort + overflow = false; + } + set_status_bit(ST_OV, overflow); + m_value_copy = m_current_value; // Save the high word + m_address = (m_address + 2) & m_prgaddr_mask; // Read next word + break; + case 2: + // W2 is in m_current_value + // Create full word and perform division + uval32 = (m_value_copy << 16) | m_current_value; + + if (TRACE_ALU) logerror("tms99xx: Dividing %08x by %04x\n", uval32, m_source_value); + m_current_value = uval32 / m_source_value; + m_value_copy = uval32 % m_source_value; + + if (TRACE_ALU) logerror("tms99xx: Quotient %04x, remainder %04x\n", m_current_value, m_value_copy); + + m_address = m_address_copy; + + // The number of ALU cycles depends on the number of steps in + // the division algorithm. The number of cycles is between 32 and + // 48 (*2 for clock cycles) + // As I don't have a description of the actual algorithm, I'll use + // the following heuristic: We use 32 ALU cycles in general, then + // we need as many cycles as it takes to + // shift away the dividend. Thus, bigger dividends need more cycles. + + pulse_clock(62); // one pulse is at the start, one at the end + value1 = m_value_copy & 0xffff; + + while (value1 != 0) + { + value1 = (value1 >> 1) & 0xffff; + pulse_clock(2); + } + // We still have m_regnumber; this is where m_current_value will go to + break; + case 3: + // Prepare to write the remainder + m_current_value = m_value_copy; + m_address = m_address + 2; + if (TRACE_STATUS) logerror("tms99xx: ST = %04x (div)\n", ST); + break; + } + pulse_clock(2); + m_state++; +} + +void tms99xx_device::alu_xop() +{ + switch (m_state) + { + case 0: + // We have the effective address of the source operand in m_address + m_address_saved = m_address; + // Now we take the XOP number from the instruction register + // and calculate the vector location + // [0010 11xx xx tt SSSS] shift 6 right, then *4 => shift 4 right + m_address = 0x0040 + ((IR >> 4) & 0x003c); + // Takes some additional cycles + pulse_clock(4); + break; + case 1: + m_value_copy = WP; // save the old WP + WP = m_current_value & m_prgaddr_mask & 0xfffe; // the new WP has been read in the previous microoperation + m_current_value = m_address_saved; // we saved the address of the source operand; retrieve it + m_address = WP + 0x0016; // Next register is R11 + break; + case 2: + m_address = WP + 0x001e; + m_current_value = ST; + break; + case 3: + m_address = WP + 0x001c; + m_current_value = PC; + break; + case 4: + m_address = WP + 0x001a; + m_current_value = m_value_copy; // old WP into new R13 + break; + case 5: + m_address = 0x0042 + ((IR >> 4) & 0x003c); // location of new PC + set_status_bit(ST_X, true); + break; + case 6: + PC = m_current_value & m_prgaddr_mask & 0xfffe; + break; + } + pulse_clock(2); + m_state++; +} + +void tms99xx_device::alu_clr_swpb() +{ + UINT32 dest_new = 0; + UINT32 src_val = m_current_value & 0x0000ffff; + UINT16 sign = 0; + + bool setstatus = true; + bool check_ov = true; + + switch (m_command) + { + case CLR: + // no status bits + m_current_value = 0x0000; + setstatus = false; + break; + case SETO: + // no status bits + m_current_value = 0xffff; + setstatus = false; + break; + case INV: + // LAE + dest_new = ~src_val & 0xffff; + check_ov = false; + break; + case NEG: + // LAECO + // Overflow occurs for value=0x8000 + dest_new = ((~src_val) & 0x0000ffff) + 1; + check_ov = false; + set_status_bit(ST_OV, src_val == 0x8000); + break; + case INC: + // LAECO + // Overflow for result value = 0x8000 + // Carry for result value = 0x0000 + dest_new = src_val + 1; + break; + case INCT: + // LAECO + // Overflow for result value = 0x8000 / 0x8001 + // Carry for result value = 0x0000 / 0x0001 + dest_new = src_val + 2; + break; + case DEC: + // LAECO + // Carry for result value != 0xffff + // Overflow for result value == 0x7fff + dest_new = src_val + 0xffff; + sign = 0x8000; + break; + case DECT: + // Carry for result value != 0xffff / 0xfffe + // Overflow for result value = 0x7fff / 0x7ffe + dest_new = src_val + 0xfffe; + sign = 0x8000; + break; + case SWPB: + m_current_value = ((m_current_value << 8) | (m_current_value >> 8)) & 0xffff; + setstatus = false; + break; + } + + if (setstatus) + { + if (check_ov) set_status_bit(ST_OV, ((src_val & 0x8000)==sign) && ((dest_new & 0x8000)!=sign)); + set_status_bit(ST_C, (dest_new & 0x10000) != 0); + m_current_value = dest_new & 0xffff; + compare_and_set_lae(m_current_value, 0); + } + + pulse_clock(2); + // No states here +} + +void tms99xx_device::alu_abs() +{ + // LAECO (from original word!) + // O if >8000 + // C is alwas reset + set_status_bit(ST_OV, m_current_value == 0x8000); + set_status_bit(ST_C, false); + compare_and_set_lae(m_current_value, 0); + + if ((m_current_value & 0x8000)!=0) + { + m_current_value = (((~m_current_value) & 0x0000ffff) + 1) & 0xffff; + pulse_clock(2); // If ABS is performed it takes one machine cycle more + } + else + { + MPC++; // skips over the next micro operation (MEMORY_WRITE) + } + pulse_clock(2); +} + +void tms99xx_device::alu_x() +{ + if (TRACE_ALU) logerror("tms99xx: Substituting current command by %04x\n", m_current_value); + decode(m_current_value); + pulse_clock(2); +} + +/* + Also used by other microprograms +*/ +void tms99xx_device::alu_b() +{ + // no status bits + // Although we got the contents of the source data, we do not use them + // but directly branch there. That is, we are only interested in the + // address of the source data. + // If we have a B *R5 and R5 contains the value 0xa000, the CPU actually + // retrieves the value at 0xa000, but in fact it will load the PC + // with the address 0xa000 + m_current_value = PC; + PC = m_address & m_prgaddr_mask & 0xfffe; + m_address = WP + 22; + if (TRACE_ALU) logerror("tms99xx: Set new PC = %04x\n", PC); + pulse_clock(2); +} + +void tms99xx_device::alu_blwp() +{ + switch (m_state) + { + case 0: + m_value_copy = WP; + WP = m_current_value & m_prgaddr_mask & 0xfffe; // set new WP (*m_destination) + m_address_saved = (m_address + 2) & m_prgaddr_mask; // Save the location of the WP + m_address = WP + 30; + m_current_value = ST; // get status register + break; + case 1: + m_current_value = PC; // get program counter + m_address = m_address - 2; + break; + case 2: + m_current_value = m_value_copy; // retrieve the old WP + m_address = m_address - 2; + break; + case 3: + m_address = m_address_saved; // point to PC component of branch vector + break; + case 4: + PC = m_current_value & m_prgaddr_mask & 0xfffe; + if (TRACE_ALU) logerror("tms9900: Context switch complete; WP=%04x, PC=%04x, ST=%04x\n", WP, PC, ST); + break; + } + pulse_clock(2); + m_state++; +} + +void tms99xx_device::alu_ldcr() +{ + UINT16 value; + + // Spec: "If the source operand address is odd, the address is truncated + // to an even address prior to data transfer." + // (Editor/Assembler, page 151) + // This refers to transfers with more than 8 bits. In this case, for + // LDCR the first bit is taken from the least significant bit of the + // source word. If the address is odd (e.g. 0x1001), it is + // treated as 0x1000, that is, truncated to an even address. + // For transfers with 1-8 bits, the first bit is the least significant + // bit of the source byte (any address). + + if (m_state == 0) + { + m_address = WP + 24; + } + else + { + value = m_source_value; // copied by ALU_SOURCE + m_count = (IR >> 6) & 0x000f; + if (m_count == 0) m_count = 16; + if (m_count <= 8) + { + if (m_source_even) value>>=8; + set_status_parity((UINT8)(value & 0xff)); + compare_and_set_lae(value<<8, 0); + } + else + { + compare_and_set_lae(value, 0); + } + m_cru_address = m_current_value; + m_value = value; + if (TRACE_CRU) logerror("tms99xx: Load CRU address %04x (%d bits), value = %04x\n", m_cru_address, m_count, m_value); + } + m_state++; + pulse_clock(2); +} + +void tms99xx_device::alu_stcr() +{ + UINT16 value; + int n = 2; + // For STCR transfers with more than 8 bits, the first CRU bit is + // always put into the least significant bit of the destination word. + // If the address is odd (e.g. 0x1001), it is treated as 0x1000, that is, + // truncated to an even boundary. + // For transfers with 1-8 bits, the destination address is handled as + // in MOVB operations, i.e. the other byte of the word is kept unchanged. + + switch (m_state) + { + case 0: // After getting the destination operand and saving the address/value + m_address = WP + 24; + n = 0; + break; + case 1: // After getting R12 + m_cru_address = m_current_value; + m_count = (IR >> 6) & 0x000f; + if (m_count == 0) m_count = 16; + break; + case 2: // After the cru operation; value starts at LSB of m_value + value = m_value & 0xffff; + if (m_count < 9) + { + if (TRACE_CRU) logerror("tms99xx: Store CRU at %04x (%d bits) in %04x, result = %02x\n", m_cru_address, m_count, m_source_address, value); + set_status_parity((UINT8)(value & 0xff)); + compare_and_set_lae(value<<8, 0); + if (m_source_even) + m_current_value = (m_source_value & 0x00ff) | (value<<8); + else + m_current_value = (m_source_value & 0xff00) | (value & 0xff); + + pulse_clock(2*(5 + (8-m_count))); + } + else + { + if (TRACE_CRU) logerror("tms99xx: Store CRU at %04x (%d bits) in %04x, result = %04x\n", m_cru_address, m_count, m_source_address, value); + m_current_value = value; + compare_and_set_lae(value, 0); + pulse_clock(2*(5 + (16-m_count))); + } + m_address = m_source_address; + break; + } + + m_state++; + pulse_clock(n); +} + +void tms99xx_device::alu_sbz_sbo() +{ + INT8 displacement; + if (m_state==0) + { + m_address = WP + 24; + } + else + { + m_value = (m_command==SBO)? 1 : 0; + displacement = (INT8)(IR & 0xff); + m_cru_address = m_current_value + (displacement<<1); + m_count = 1; + } + m_state++; + pulse_clock(2); +} + +void tms99xx_device::alu_tb() +{ + INT8 displacement; + switch (m_state) + { + case 0: + m_address = WP + 24; + break; + case 1: + displacement = (INT8)(IR & 0xff); + m_cru_address = m_current_value + (displacement<<1); + m_count = 1; + break; + case 2: + set_status_bit(ST_EQ, m_value!=0); + if (TRACE_STATUS) logerror("tms99xx: ST = %04x\n", ST); + break; + } + m_state++; + pulse_clock(2); +} + +void tms99xx_device::alu_jmp() +{ + INT8 displacement; + bool cond = false; + + if (m_state==0) + { + switch (m_command) + { + case JMP: + cond = true; + break; + case JLT: // LAECOP == x00xxx + cond = ((ST & (ST_AGT | ST_EQ))==0); + break; + case JLE: // LAECOP == 0xxxxx + cond = ((ST & ST_LH)==0); + break; + case JEQ: // LAECOP == xx1xxx + cond = ((ST & ST_EQ)!=0); + break; + case JHE: // LAECOP == 1x0xxx, 0x1xxx + cond = ((ST & (ST_LH | ST_EQ)) != 0); + break; + case JGT: // LAECOP == x1xxxx + cond = ((ST & ST_AGT)!=0); + break; + case JNE: // LAECOP == xx0xxx + cond = ((ST & ST_EQ)==0); + break; + case JNC: // LAECOP == xxx0xx + cond = ((ST & ST_C)==0); + break; + case JOC: // LAECOP == xxx1xx + cond = ((ST & ST_C)!=0); + break; + case JNO: // LAECOP == xxxx0x + cond = ((ST & ST_OV)==0); + break; + case JL: // LAECOP == 0x0xxx + cond = ((ST & (ST_LH | ST_EQ)) == 0); + break; + case JH: // LAECOP == 1xxxxx + cond = ((ST & ST_LH)!=0); + break; + case JOP: // LAECOP == xxxxx1 + cond = ((ST & ST_OP)!=0); + break; + } + if (!cond) + { + if (TRACE_ALU) logerror("tms99xx: Jump condition false\n"); + MPC+=1; // skip next ALU call + } + else + if (TRACE_ALU) logerror("tms99xx: Jump condition true\n"); + } + else + { + displacement = (IR & 0xff); + PC = (PC + (displacement<<1)) & m_prgaddr_mask & 0xfffe; + } + m_state++; + pulse_clock(2); +} + +void tms99xx_device::alu_shift() +{ + bool carry = false; + bool overflow = false; + UINT16 sign = 0; + UINT32 value; + int count; + + switch (m_state) + { + case 0: + m_address = WP + ((IR & 0x000f)<<1); + pulse_clock(2); + break; + case 1: + // we have the value of the register in m_current_value + // Save it (we may have to read R0) + m_value_copy = m_current_value; + m_address_saved = m_address; + m_address = WP; + m_current_value = (IR >> 4) & 0x000f; + + if (m_current_value != 0) + { + // skip the next read and ALU operation + MPC = MPC+2; + m_state++; + } + else + { + if (TRACE_ALU) logerror("tms99xx: Shift operation gets count from R0\n"); + pulse_clock(2); + } + pulse_clock(2); + break; + case 2: + // after READ + pulse_clock(2); + pulse_clock(2); + break; + case 3: + count = m_current_value & 0x000f; // from the instruction or from R0 + if (count==0) count = 16; + + value = m_value_copy; + + // we are re-implementing the shift operations because we have to pulse + // the clock at each single shift anyway. + // Also, it is easier to implement the status bit setting. + // Note that count is never 0 + if (m_command == SRA) sign = value & 0x8000; + + for (int i=0; i < count; i++) + { + switch (m_command) + { + case SRL: + case SRA: + carry = ((value & 1)!=0); + value = (value >> 1) | sign; + break; + case SLA: + carry = ((value & 0x8000)!=0); + value <<= 1; + if (carry != ((value&0x8000)!=0)) overflow = true; + break; + case SRC: + carry = ((value & 1)!=0); + value = (value>>1) | (carry? 0x8000 : 0x0000); + break; + } + pulse_clock(2); + } + + m_current_value = value & 0xffff; + set_status_bit(ST_C, carry); + set_status_bit(ST_OV, overflow); + compare_and_set_lae(m_current_value, 0); + m_address = m_address_saved; // Register address + if (TRACE_STATUS) logerror("tms99xx: ST = %04x (val=%04x)\n", ST, m_current_value); + break; + } + m_state++; +} + +void tms99xx_device::alu_ai_ori() +{ + UINT32 dest_new = 0; + switch (m_command) + { + case AI: + dest_new = m_current_value + m_value_copy; + // See status bit handling for Add + set_status_bit(ST_C, (dest_new & 0x10000) != 0); + set_status_bit(ST_OV, ((dest_new ^ m_current_value) & (dest_new ^ m_value_copy) & 0x8000)!=0); + break; + case ANDI: + dest_new = m_current_value & m_value_copy; + break; + case ORI: + dest_new = m_current_value | m_value_copy; + break; + } + m_current_value = dest_new & 0xffff; + m_address = m_address_copy; + compare_and_set_lae(m_current_value, 0); + pulse_clock(2); +} + +void tms99xx_device::alu_ci() +{ + compare_and_set_lae(m_value_copy, m_current_value); + pulse_clock(2); +} + +void tms99xx_device::alu_li() +{ + compare_and_set_lae(m_current_value, 0); + pulse_clock(2); +} + +void tms99xx_device::alu_lwpi() +{ + WP = m_current_value & m_prgaddr_mask & 0xfffe; + pulse_clock(2); +} + +void tms99xx_device::alu_limi() +{ + ST = (ST & 0xfff0) | (m_current_value & 0x000f); + if (TRACE_STATUS) logerror("tms99xx: ST = %04x\n", ST); + pulse_clock(2); +} + +void tms99xx_device::alu_stwp_stst() +{ + if (m_command==STST) m_current_value = ST; + else m_current_value = WP; + pulse_clock(2); +} + +void tms99xx_device::alu_external() +{ + // Call some possibly attached external device + // We pass the bit pattern of the address bus to the external function + + // IDLE = 0000 0011 0100 0000 + // RSET = 0000 0011 0110 0000 + // CKON = 0000 0011 1010 0000 + // CKOF = 0000 0011 1100 0000 + // LREX = 0000 0011 1110 0000 + // --- + if (m_command == IDLE) + m_idle_state = true; + + if (!m_external_operation.isnull()) m_external_operation((IR >> 5) & 0x07, 1, 0xff); + pulse_clock(2); +} + +void tms99xx_device::alu_rtwp() +{ + switch (m_state) + { + case 0: + m_address = WP + 30; // R15 + pulse_clock(2); + break; + case 1: + ST = m_current_value; + m_address -= 2; // R14 + break; + case 2: + PC = m_current_value & m_prgaddr_mask & 0xfffe; + m_address -= 2; // R13 + break; + case 3: + WP = m_current_value & m_prgaddr_mask & 0xfffe; + pulse_clock(2); + break; + } + m_state++; +} + + +void tms99xx_device::alu_int() +{ + if (TRACE_EMU) logerror("tms99xx: INT state %d; irq_level %d\n", m_state, m_irq_level); + switch (m_state) + { + case 0: + if (m_irq_level == RESET_INT) + { + m_address = 0; + pulse_clock(2); + } + else + { + if (m_irq_level == LOAD_INT) m_address = 0xfffc; // will be truncated for TMS9980 + else + { + m_address = (m_irq_level << 2); + } + } + break; + case 1: + m_address_copy = m_address; + m_value_copy = WP; // old WP + WP = m_current_value & m_prgaddr_mask & 0xfffe; // new WP + m_current_value = ST; + m_address = (WP + 30) & m_prgaddr_mask; + break; + case 2: + m_current_value = PC; + m_address = (WP + 28) & m_prgaddr_mask; + break; + case 3: + m_current_value = m_value_copy; // old WP + m_address = (WP + 26) & m_prgaddr_mask; + break; + case 4: + m_address = (m_address_copy + 2) & 0xfffe & m_prgaddr_mask; + if (TRACE_ALU) logerror("tms99xx: read from %04x\n", m_address); + break; + case 5: + PC = m_current_value & m_prgaddr_mask & 0xfffe; + if (m_irq_level > 0 ) + { + ST = (ST & 0xfff0) | (m_irq_level - 1); + } + break; + } + m_state++; + pulse_clock(2); +} + +/**************************************************************************/ +UINT32 tms99xx_device::execute_min_cycles() const +{ + return 2; +} + +// TODO: Compute this value, just a wild guess for the average +UINT32 tms99xx_device::execute_max_cycles() const +{ + return 10; +} + +UINT32 tms99xx_device::execute_input_lines() const +{ + return 2; +} + +// clocks to cycles, cycles to clocks = id +// execute_default_irq_vector = 0 +// execute_burn = nop + +// device_disasm_interface overrides +UINT32 tms99xx_device::disasm_min_opcode_bytes() const +{ + return 2; +} + +UINT32 tms99xx_device::disasm_max_opcode_bytes() const +{ + return 6; +} + +offs_t tms99xx_device::disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options) +{ + extern CPU_DISASSEMBLE( tms9900 ); + return CPU_DISASSEMBLE_NAME(tms9900)(this, buffer, pc, oprom, opram, options); +} + + +const device_type TMS9900 = &device_creator<tms9900_device>; diff --git a/src/devices/cpu/tms9900/tms9900.h b/src/devices/cpu/tms9900/tms9900.h new file mode 100644 index 00000000000..8e7f19042c6 --- /dev/null +++ b/src/devices/cpu/tms9900/tms9900.h @@ -0,0 +1,403 @@ +// license:BSD-3-Clause +// copyright-holders:Michael Zapf +/* + TMS9900 processor + This is a re-implementation of the TMS9900 featuring a cycle-precise + behaviour. + + See tms9900.c for documentation +*/ + +#ifndef __TMS9900_H__ +#define __TMS9900_H__ + +#include "emu.h" +#include "debugger.h" +#include "tms99com.h" + +enum +{ + INT_9900_RESET = 0, + INT_9900_LOAD = 1, + INT_9900_INTREQ = 2 +}; + +enum +{ + LOAD_INT = -1, + RESET_INT = -2 +}; + +static const char opname[][5] = +{ "ILL ", "A ", "AB ", "ABS ", "AI ", "ANDI", "B ", "BL ", "BLWP", "C ", + "CB ", "CI ", "CKOF", "CKON", "CLR ", "COC ", "CZC ", "DEC ", "DECT", "DIV ", + "IDLE", "INC ", "INCT", "INV ", "JEQ ", "JGT ", "JH ", "JHE ", "JL ", "JLE ", + "JLT ", "JMP ", "JNC ", "JNE ", "JNO ", "JOC ", "JOP ", "LDCR", "LI ", "LIMI", + "LREX", "LWPI", "MOV ", "MOVB", "MPY ", "NEG ", "ORI ", "RSET", "RTWP", "S ", + "SB ", "SBO ", "SBZ ", "SETO", "SLA ", "SOC ", "SOCB", "SRA ", "SRC ", "SRL ", + "STCR", "STST", "STWP", "SWPB", "SZC ", "SZCB", "TB ", "X ", "XOP ", "XOR ", + "*int" +}; + +class tms99xx_device : public cpu_device +{ +public: + tms99xx_device(const machine_config &mconfig, device_type type, const char *name, + const char *tag, int databus_width, int prg_addr_bits, int cru_addr_bits, + device_t *owner, UINT32 clock, const char *shortname, const char *source); + + ~tms99xx_device(); + + // READY input line. When asserted (high), the memory is ready for data exchange. + void set_ready(int state); + + // HOLD input line. When asserted (low), the CPU is requested to release the + // data and address bus and enter the HOLD state. The entrance of this state + // is acknowledged by the HOLDA output line. + void set_hold(int state); + + // Callbacks + template<class _Object> static devcb_base &static_set_extop_callback(device_t &device, _Object object) { return downcast<tms99xx_device &>(device).m_external_operation.set_callback(object); } + template<class _Object> static devcb_base &static_set_intlevel_callback(device_t &device, _Object object) { return downcast<tms99xx_device &>(device).m_get_intlevel.set_callback(object); } + template<class _Object> static devcb_base &static_set_iaq_callback(device_t &device, _Object object) { return downcast<tms99xx_device &>(device).m_iaq_line.set_callback(object); } + template<class _Object> static devcb_base &static_set_clkout_callback(device_t &device, _Object object) { return downcast<tms99xx_device &>(device).m_clock_out_line.set_callback(object); } + template<class _Object> static devcb_base &static_set_wait_callback(device_t &device, _Object object) { return downcast<tms99xx_device &>(device).m_wait_line.set_callback(object); } + template<class _Object> static devcb_base &static_set_holda_callback(device_t &device, _Object object) { return downcast<tms99xx_device &>(device).m_holda_line.set_callback(object); } + template<class _Object> static devcb_base &static_set_dbin_callback(device_t &device, _Object object) { return downcast<tms99xx_device &>(device).m_dbin_line.set_callback(object); } + +protected: + // device-level overrides + virtual void device_start(); + virtual void device_stop(); + virtual void device_reset(); + + virtual void resolve_lines(); + + // device_execute_interface overrides + virtual UINT32 execute_min_cycles() const; + virtual UINT32 execute_max_cycles() const; + virtual UINT32 execute_input_lines() const; + virtual void execute_set_input(int irqline, int state); + virtual void execute_run(); + + // device_disasm_interface overrides + virtual UINT32 disasm_min_opcode_bytes() const; + virtual UINT32 disasm_max_opcode_bytes() const; + virtual offs_t disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options); + + const address_space_config* memory_space_config(address_spacenum spacenum) const; + + // Let these methods be overloaded by the TMS9980. + virtual void mem_read(void); + virtual void mem_write(void); + virtual void acquire_instruction(void); + void decode(UINT16 inst); + + const address_space_config m_program_config; + const address_space_config m_io_config; + address_space* m_prgspace; + address_space* m_cru; + + virtual UINT16 read_workspace_register_debug(int reg); + virtual void write_workspace_register_debug(int reg, UINT16 data); + + // Cycle counter + int m_icount; + + // TMS9900 hardware registers + UINT16 WP; // Workspace pointer + UINT16 PC; // Program counter + UINT16 ST; // Status register + + // Internal register + UINT16 IR; // Instruction register + + // Stored address + UINT16 m_address; + + // Stores the recently read word or the word to be written + UINT16 m_current_value; + + // Decoded command + UINT16 m_command; + + // Is it a byte operation? Only format 1 commands with the byte flag set + // and CRU commands with less than 9 bits to transfer are byte operations. + bool m_byteop; + + // Issue clock pulses. Note that each machine cycle has two clock cycles. + void pulse_clock(int count); + + // For multi-pass operations. For instance, memory word accesses are + // executed as two consecutive byte accesses. CRU accesses are repeated + // single-bit accesses. (Needed for TMS9980) + int m_pass; + + // Data bus width. Needed for TMS9980. + int m_databus_width; + + // Check the READY line? + bool m_check_ready; + + // Phase of the memory access + int m_mem_phase; + + // Max address + const UINT16 m_prgaddr_mask; + const UINT16 m_cruaddr_mask; + + bool m_load_state; + bool m_irq_state; + bool m_reset; + + // Determine the interrupt level using the IC0-IC3 lines + int get_intlevel(int state); + + // Interrupt level as acquired from input lines (TMS9900: IC0-IC3, TMS9980: IC0-IC2) + // We assume all values right-justified, i.e. TMS9980 also counts up by one + int m_irq_level; + + // Used to display the number of consumed cycles in the log. + int m_first_cycle; + + /************************************************************************/ + + // Clock output. This is not a pin of the TMS9900 because the TMS9900 + // needs an external clock, and usually one of those external lines is + // used for this purpose. + devcb_write_line m_clock_out_line; + + // Wait output. When asserted (high), the CPU is in a wait state. + devcb_write_line m_wait_line; + + // HOLD Acknowledge line. When asserted (high), the CPU is in HOLD state. + devcb_write_line m_holda_line; + + // Signal to the outside world that we are now getting an instruction + devcb_write_line m_iaq_line; + + // Get the value of the interrupt level lines + devcb_read8 m_get_intlevel; + + // DBIN line. When asserted (high), the CPU has disabled the data bus output buffers. + devcb_write_line m_dbin_line; + + // Trigger external operation. This is achieved by putting a special value in + // the most significant three bits of the address bus (TMS9995: data bus) and + // pulsing the CRUCLK line. + // Accordingly, we have + // + // A0 A1 A2 A3 A4 A5 ... A12 A13 A14 A15 + // 0 0 0 x x x x x x - normal CRU access + // 0 1 0 x x x x x x - IDLE + // 0 1 1 x x x x x x - RSET + // 1 0 1 x x x x x x - CKON + // 1 1 0 x x x x x x - CKOF + // 1 1 1 x x x x x x - LREX + // + // so the TMS9900 can only use CRU addresses 0 - 1ffe for CRU operations. + // By moving these three bits to the data bus, the TMS9995 can allow for the + // full range 0000-fffe for its CRU operations. + // + // We could realize this via the CRU access as well, but the data bus access + // is not that simple to emulate. For the sake of homogenity between the + // chip emulations we use a dedicated callback. + devcb_write8 m_external_operation; + + +private: + // Indicates if this is a byte-oriented command + inline bool byte_operation(); + + // Processor states + bool m_idle_state; + + // READY handling. The READY line is operated before the phi1 clock + // pulse rises. As the ready line is only set once in this emulation we + // keep the level in a buffer (like a latch) + bool m_ready_bufd; // buffered state + bool m_ready; // sampled value + + bool m_wait_state; + bool m_hold_state; + + // State / debug management + UINT16 m_state_any; + static const char* s_statename[]; + void state_import(const device_state_entry &entry); + void state_export(const device_state_entry &entry); + void state_string_export(const device_state_entry &entry, std::string &str); + + // Interrupt handling + void service_interrupt(); + + // ================ Microprogram support ======================== + + // Set up lookup table + void build_command_lookup_table(); + + // Sequence of micro-operations + typedef const UINT8* microprogram; + + // Method pointer + typedef void (tms99xx_device::*ophandler)(void); + + // Opcode list entry + struct tms_instruction + { + UINT16 opcode; + int id; + int format; + microprogram prog; // Microprogram + }; + + // Lookup table entry + struct lookup_entry + { + lookup_entry *next_digit; + const tms_instruction *entry; + }; + + // Pointer to the lookup table + lookup_entry* m_command_lookup_table; + + // List of allocated tables (used for easy clean-up on exit) + lookup_entry* m_lotables[32]; + + // List of pointers for micro-operations + static const tms99xx_device::ophandler s_microoperation[]; + + // Opcode table + static const tms99xx_device::tms_instruction s_command[]; + + // Micro-operation declarations + void register_read(void); + void register_write(void); + void cru_input_operation(void); + void cru_output_operation(void); + void data_derivation_subprogram(void); + void return_from_subprogram(void); + void command_completed(void); + + void alu_nop(void); + void alu_clear(void); + void alu_source(void); + void alu_setaddr(void); + void alu_addone(void); + void alu_setaddr_addone(void); + void alu_pcaddr_advance(void); + void alu_add_register(void); + + void alu_imm(void); + void alu_reg(void); + + void alu_f1(void); + void alu_comp(void); + void alu_f3(void); + void alu_multiply(void); + void alu_divide(void); + void alu_xop(void); + void alu_clr_swpb(void); + void alu_abs(void); + void alu_x(void); + void alu_b(void); + void alu_bl(void); + void alu_blwp(void); + void alu_ldcr(void); + void alu_stcr(void); + void alu_sbz_sbo(void); + void alu_tb(void); + void alu_jmp(void); + void alu_shift(void); + void alu_ai_ori(void); + void alu_ci(void); + void alu_li(void); + void alu_lwpi(void); + void alu_limi(void); + void alu_stwp_stst(void); + void alu_external(void); + void alu_rtwp(void); + void alu_int(void); + + void abort_operation(void); + + // Micro-operation + UINT8 m_op; + + // Micro-operation program counter (as opposed to the program counter PC) + int MPC; + + // Current microprogram + const UINT8* m_program; + + // Calling microprogram (used when data derivation is called) + const UINT8* m_caller; + int m_caller_MPC; + + // State of the micro-operation. Needed for repeated ALU calls. + int m_state; + + // Has HOLD been acknowledged yet? + bool m_hold_acknowledged; + + // Signal the wait state via the external line + inline void set_wait_state(bool state); + + // Used to acknowledge HOLD and enter the HOLD state + inline void acknowledge_hold(); + + // Was the source operand a byte from an even address? + bool m_source_even; + + // Was the destination operand a byte from an even address? + bool m_destination_even; + + // Intermediate storage for the source operand + UINT16 m_source_address; + UINT16 m_source_value; + UINT16 m_address_saved; + + // Another copy of the address + UINT16 m_address_copy; + + // Stores the recently read register contents + UINT16 m_register_contents; + + // Stores the register number for the next register access + int m_regnumber; + + // CRU support: Stores the CRU address + UINT16 m_cru_address; + + // CRU support: Stores the number of bits to be transferred + int m_count; + + // Copy of the value + UINT16 m_value_copy; + + // Another internal register, storing intermediate values + // Using 32 bits to support MPY + UINT32 m_value; + + // For two-argument commands. Indicates whether this is the second operand. + bool m_get_destination; + + // Status register update + inline void set_status_bit(int bit, bool state); + inline void compare_and_set_lae(UINT16 value1, UINT16 value2); + void set_status_parity(UINT8 value); +}; + +/*****************************************************************************/ + +class tms9900_device : public tms99xx_device +{ +public: + tms9900_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); +}; + + +// device type definition +extern const device_type TMS9900; + +#endif /* __TMS9900_H__ */ diff --git a/src/devices/cpu/tms9900/tms9980a.c b/src/devices/cpu/tms9900/tms9980a.c new file mode 100644 index 00000000000..054a293a403 --- /dev/null +++ b/src/devices/cpu/tms9900/tms9980a.c @@ -0,0 +1,300 @@ +// license:BSD-3-Clause +// copyright-holders:Michael Zapf +/* + Cycle-precise implementation of the TMS9980A. + Subclassed from tms99xx_device in tms9900.c. + + +----------------+ + /HOLD | 1 \/ 40| /MEMEN + HOLDA | 2 39| READY + IAQ | 3 38| /WE + LSB +- A13,CRUOUT | 4 37| CRUCLK + | A12 | 5 36| Vdd + | A11 | 6 35| Vss + | A10 | 7 34| CKIN + Address A9 | 8 33| D7 --+ + bus A8 | 9 32| D6 | + | A7 |10 31| D5 Data + 16KiB A6 |11 30| D4 bus + | A5 |12 29| D3 | + | A4 |13 28| D2 2 * 8 bit + | A3 |14 27| D1 | + | A2 |15 26| D0 --+ + | A1 |16 25| INT0 --+ + MSB +-- A0 |17 24| INT1 | Interrupt levels + DBIN |18 23| INT2 --+ + CRUIN |19 22| /PHI3 + Vcc |20 21| Vbb + +----------------+ + + The TMS9980A is similar to the TMS9900, with the following differences: + + - Address bus is only 14 bit wide (16 KiB) + - Data bus is 16 bit wide and multiplexed on 8 lines (2 bytes per access) + - CRU space is limited to 2048 bits (due to fewer address lines) + - Only three interrupt level lines, for a maximum of 8 levels. + - No INTREQ, RESET, and LOAD lines. All interrupts are signaled via INT0 - + INT2. Reset=00x, Load=010, Level1=011, Level2=100, Level3=101, Level4=110, + all interrupts cleared=111. + - Memory accesses are always 2 bytes (even address byte, odd address byte) + even for byte operations. Thus the 9980A, like the TMS9900, needs to + pre-fetch the word at the destination before overwriting it. + - On the cycle level both TMS9900 and TMS9980A are equal, except for the + additional cycles needed for memory read and write access. Accordingly, + the emulation shares the core and the microprograms and redefines the + memory access and the interrupt handling only. + - The 9980A has the same external instructions as the TMS9900, but it + indicates the command via A0, A1, and A13 (instead of A0-A2). + + For pin definitions see tms9900.c + + Michael Zapf, 2012 +*/ + +#include "tms9980a.h" + +/* + The following defines can be set to 0 or 1 to disable or enable certain + output in the log. +*/ + +// Memory operation +#define TRACE_MEM 0 + +// Address bus operation +#define TRACE_ADDRESSBUS 0 + +// Log operation +#define TRACE_OP 0 + +// Interrupts +#define TRACE_INT 0 + +/**************************************************************************** + Constructor +****************************************************************************/ + +tms9980a_device::tms9980a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : tms99xx_device(mconfig, TMS9980A, "TMS9980A", tag, 8, 14, 11, owner, clock, "tms9980a", __FILE__) +{ +} + +/* + External connections +*/ +void tms9980a_device::resolve_lines() +{ + // Resolve our external connections + m_external_operation.resolve(); + m_iaq_line.resolve(); + m_clock_out_line.resolve(); + m_holda_line.resolve(); + m_dbin_line.resolve(); +} + +UINT16 tms9980a_device::read_workspace_register_debug(int reg) +{ + int temp = m_icount; + int addr = (WP+(reg<<1)) & 0xfffe & m_prgaddr_mask; + UINT16 value = (m_prgspace->read_byte(addr) << 8) | (m_prgspace->read_byte(addr+1) & 0xff); + m_icount = temp; + return value; +} + +void tms9980a_device::write_workspace_register_debug(int reg, UINT16 data) +{ + int temp = m_icount; + int addr = (WP+(reg<<1)) & 0xfffe & m_prgaddr_mask; + m_prgspace->write_byte(addr, data>>8); + m_prgspace->write_byte(addr+1, data & 0xff); + m_icount = temp; +} + +/* + Interrupt input. Keep in mind that the TMS9980A does not have any INTREQ + line but signals interrupts via IC0-IC2 only. Thus we cannot take down any + single interrupt; only all interrupts can be cleared at once using level 7. + The state parameter is actually not needed. +*/ +void tms9980a_device::execute_set_input(int irqline, int state) +{ + // We model the three lines IC0-IC2 as 8 separate input lines, although we + // cannot assert more than one at a time. The state value is not needed, + // as level 7 means to clean all interrupts, but we consider it for the + // sake of consistency. + + int level = irqline; + + // Just to stay consistent. + if (state==CLEAR_LINE) level = INT_9980A_CLEAR; + + switch (level) + { + case INT_9980A_RESET: + case 1: + level = RESET_INT; + m_reset = true; + break; + case INT_9980A_LOAD: + level = LOAD_INT; + break; + case INT_9980A_LEVEL1: + case INT_9980A_LEVEL2: + case INT_9980A_LEVEL3: + case INT_9980A_LEVEL4: + level = level - 2; + break; + case INT_9980A_CLEAR: + // Clear all interrupts + m_load_state = false; + m_irq_state = false; + if (TRACE_INT) logerror("tms9980a: clear interrupts\n"); + break; + } + + m_irq_level = level; + + if (m_irq_level != INT_9980A_CLEAR) + { + if (m_irq_level == LOAD_INT) + { + // Some boards start up with LOAD interrupt, so we clear the reset flag + m_reset = false; + m_load_state = true; + } + else m_irq_state = true; + if (TRACE_INT) logerror("tms9980a: interrupt level=%d, ST=%04x\n", m_irq_level, ST); + } +} + +/*****************************************************************************/ + +/* + Memory read: + Clock cycles: 4 + 2W, W = number of wait states +*/ +void tms9980a_device::mem_read() +{ + UINT8 value; + switch (m_mem_phase) + { + case 1: + m_pass = 4; // make the CPU visit this method more than once + if (!m_dbin_line.isnull()) m_dbin_line(ASSERT_LINE); + m_prgspace->set_address(m_address & m_prgaddr_mask & ~1); + if (TRACE_ADDRESSBUS) logerror("tms9980a: set address bus %04x\n", m_address & m_prgaddr_mask & ~1); + m_check_ready = true; + break; + case 2: + // Sample the value on the data bus (high byte) + value = m_prgspace->read_byte(m_address & m_prgaddr_mask & ~1); + if (TRACE_MEM) logerror("tms9980a: memory read high byte %04x -> %02x\n", m_address & m_prgaddr_mask & ~1, value); + m_current_value = (value << 8) & 0xff00; + break; + case 3: + m_prgspace->set_address((m_address & m_prgaddr_mask) | 1); + if (TRACE_ADDRESSBUS) logerror("tms9980a: set address bus %04x\n", (m_address & m_prgaddr_mask) | 1); + break; + case 4: + // Sample the value on the data bus (low byte) + value = m_prgspace->read_byte((m_address & m_prgaddr_mask) | 1); + m_current_value = m_current_value | (value & 0x00ff); + if (TRACE_MEM) logerror("tms9980a: memory read low byte %04x -> %02x -> complete word %04x\n", (m_address & m_prgaddr_mask) | 1, value, m_current_value); + break; + } + pulse_clock(1); + m_mem_phase = (m_mem_phase % 4) +1; +} + + +void tms9980a_device::mem_write() +{ + switch (m_mem_phase) + { + case 1: + m_pass = 4; // make the CPU visit this method once more + if (!m_dbin_line.isnull()) m_dbin_line(CLEAR_LINE); + m_prgspace->set_address(m_address & m_prgaddr_mask & ~1); + if (TRACE_ADDRESSBUS) logerror("tms9980a: set address bus %04x\n", m_address & m_prgaddr_mask & ~1); + m_prgspace->write_byte(m_address & 0x3ffe & ~1, (m_current_value >> 8)&0xff); + if (TRACE_MEM) logerror("tms9980a: memory write high byte %04x <- %02x\n", m_address & m_prgaddr_mask & ~1, (m_current_value >> 8)&0xff); + m_check_ready = true; + break; + case 2: + // no action here, just wait for READY + break; + case 3: + m_prgspace->set_address((m_address & m_prgaddr_mask) | 1); + if (TRACE_ADDRESSBUS) logerror("tms9980a: set address bus %04x\n", (m_address & m_prgaddr_mask) | 1); + m_prgspace->write_byte((m_address & m_prgaddr_mask) | 1, m_current_value & 0xff); + if (TRACE_MEM) logerror("tms9980a: memory write low byte %04x <- %02x\n", (m_address & m_prgaddr_mask) | 1, m_current_value & 0xff); + break; + case 4: + // no action here, just wait for READY + break; + } + pulse_clock(1); + m_mem_phase = (m_mem_phase % 4) +1; +} + +void tms9980a_device::acquire_instruction() +{ + if (m_mem_phase == 1) + { + if (!m_iaq_line.isnull()) m_iaq_line(ASSERT_LINE); + m_address = PC; + m_first_cycle = m_icount; + } + mem_read(); + + if (m_mem_phase == 1) // changed by mem_read and wrapped + { + decode(m_current_value); + if (TRACE_OP) logerror("tms9980a: ===== Next operation %04x (%s) at %04x =====\n", IR, opname[m_command], PC); + debugger_instruction_hook(this, PC); + PC = (PC + 2) & 0xfffe & m_prgaddr_mask; + } + // IAQ will be cleared in the main loop +} + + + +/**************************************************************************/ +UINT32 tms9980a_device::execute_min_cycles() const +{ + return 2; +} + +// TODO: Compute this value, just a wild guess for the average +UINT32 tms9980a_device::execute_max_cycles() const +{ + return 10; +} + +UINT32 tms9980a_device::execute_input_lines() const +{ + return 8; +} + +// clocks to cycles, cycles to clocks = id +// execute_default_irq_vector = 0 +// execute_burn = nop + +// device_disasm_interface overrides +UINT32 tms9980a_device::disasm_min_opcode_bytes() const +{ + return 2; +} + +UINT32 tms9980a_device::disasm_max_opcode_bytes() const +{ + return 6; +} + +offs_t tms9980a_device::disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options) +{ + extern CPU_DISASSEMBLE( tms9980 ); + return CPU_DISASSEMBLE_NAME(tms9980)(this, buffer, pc, oprom, opram, options); +} + +const device_type TMS9980A = &device_creator<tms9980a_device>; diff --git a/src/devices/cpu/tms9900/tms9980a.h b/src/devices/cpu/tms9900/tms9980a.h new file mode 100644 index 00000000000..82492d6073a --- /dev/null +++ b/src/devices/cpu/tms9900/tms9980a.h @@ -0,0 +1,56 @@ +// license:BSD-3-Clause +// copyright-holders:Michael Zapf +/* + TMS9980A. + See tms9980a.c and tms9900.c for documentation +*/ + +#ifndef __TMS9980A_H__ +#define __TMS9980A_H__ + +#include "emu.h" +#include "debugger.h" +#include "tms9900.h" + +enum +{ + INT_9980A_RESET = 0, + INT_9980A_LOAD = 2, + INT_9980A_LEVEL1 = 3, + INT_9980A_LEVEL2 = 4, + INT_9980A_LEVEL3 = 5, + INT_9980A_LEVEL4 = 6, + INT_9980A_CLEAR= 7 +}; + +class tms9980a_device : public tms99xx_device +{ +public: + tms9980a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + +protected: + void mem_read(void); + void mem_write(void); + void acquire_instruction(void); + + void resolve_lines(); + + UINT16 read_workspace_register_debug(int reg); + void write_workspace_register_debug(int reg, UINT16 data); + + UINT32 execute_min_cycles() const; + UINT32 execute_max_cycles() const; + UINT32 execute_input_lines() const; + void execute_set_input(int irqline, int state); + + UINT32 disasm_min_opcode_bytes() const; + UINT32 disasm_max_opcode_bytes() const; + offs_t disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options); + address_space_config m_program_config80; + address_space_config m_io_config80; +}; + +// device type definition +extern const device_type TMS9980A; + +#endif /* __TMS9980A_H__ */ diff --git a/src/devices/cpu/tms9900/tms9995.c b/src/devices/cpu/tms9900/tms9995.c new file mode 100644 index 00000000000..ae9248fd82d --- /dev/null +++ b/src/devices/cpu/tms9900/tms9995.c @@ -0,0 +1,3466 @@ +// license:BSD-3-Clause +// copyright-holders:Michael Zapf +/* + Texas Instruments TMS9995 + + +----------------+ + XTAL1 | 1 \/ 40| A15,CRUOUT + XTAL2,CLKIN | 2 39| A14 + CLKOUT | 3 38| A13 + D7 | 4 37| A12 + D6 | 5 36| A11 + D5 | 6 35| A10 + D4 | 7 34| A9 + D3 | 8 33| A8 + D2 | 9 32| A7 + Vcc |10 31| Vss + D1 |11 30| A6 + D0 |12 29| A5 + CRUIN |13 28| A4 + /INT4,/EC |14 27| A3 + /INT1 |15 26| A2 + IAQ,HOLDA |16 25| A1 + /DBIN |17 24| A0 + /HOLD |18 23| READY + /WE,/CRUCLK |19 22| /RESET + /MEMEN |20 21| /NMI + +----------------+ + + XTAL1 in Crystal input pin for internal oscillator + XTAL2 in Crystal input pin for internal oscillator, or + CLKIN in Input pin for external oscillator + CLKOUT out Clock output signal (1:4 of the input signal frequency) + CRUIN in CRU input data + /INT4 in Interrupt level 4 input + /EC in Event counter + /INT1 in Interrupt level 1 input + IAQ out Instruction acquisition + HOLDA out Hold acknowledge + /WE out Data available for memory write + /CRUCLK out Communication register unit clock output + /MEMEN out Address bus contains memory address + /NMI in Non-maskable interrupt (/LOAD on TMS9900) + /RESET in Reset interrupt + READY in Memory/External CRU device ready for access + CRUOUT out Communication register unit data output + + Vcc +5V supply + Vss 0V Ground reference + + A0-A15 out Address bus + D0-D7 in/out Data bus + + Note that Texas Instruments' bit numberings define bit 0 as the + most significant bit (different to most other systems). Also, the + system uses big-endian memory organisation: Storing the word 0x1234 at + address 0x0000 means that the byte 0x12 is stored at 0x0000 and byte 0x34 + is stored at 0x0001. + + The TMS9995 is a 16 bit microprocessor like the TMS9900, operating on + 16-bit words and using 16-bit opcodes. Memory transfer of 16-bit words + is achieved by a transfer of the most significant byte, followed by + the least significant byte. + + The 8-bit databus width allows the processor to exchange single bytes with + the external memory. + + See tms9900.c for some more details on the cycle-precise implementation. + + This implementation also features all control lines and the instruction + prefetch mechanism. Prefetching is explicitly triggered within the + microprograms. The TMS9995 specification does not reveal the exact + operations during the microprogram execution, so we have to look at the + required cycle numbers to guess what is happening. + + Auto wait state: + + In order to enable automatic wait state creation, the READY line must be + cleared on reset time. A good position to do this is MACHINE_RESET in + the driver. + + + References (see comments below) + ---------- + [1] Texas Instruments 9900 Microprocessor series: TMS9995 16-bit Microcomputer + + TODO: + - State save + - Test HOLD + + Michael Zapf, June 2012 +*/ + +#include "tms9995.h" + +/* tms9995 ST register bits. */ +enum +{ + ST_LH = 0x8000, // Logical higher (unsigned comparison) + ST_AGT = 0x4000, // Arithmetical greater than (signed comparison) + ST_EQ = 0x2000, // Equal + ST_C = 0x1000, // Carry + ST_OV = 0x0800, // Overflow (when using signed operations) + ST_OP = 0x0400, // Odd parity (used with byte operations) + ST_X = 0x0200, // XOP + ST_OE = 0x0020, // Overflow interrupt enabled + ST_IM = 0x000f // Interrupt mask +}; + +enum +{ + PENDING_NMI = 1, + PENDING_MID = 2, + PENDING_LEVEL1 = 4, + PENDING_OVERFLOW = 8, + PENDING_DECR = 16, + PENDING_LEVEL4 = 32 +}; + +/***************************************************************** + Debugging + Set to 0 (disable) or 1 (enable) +******************************************************************/ + +// Log addresses of executed opcodes +#define TRACE_EXEC 0 + +// Log cycles +#define TRACE_CYCLES 0 + +// Log configuration +#define TRACE_CONFIG 1 + +// Log emulation details +#define TRACE_EMU 0 + +// Log wait/hold states +#define TRACE_WAITHOLD 0 + +// Log microinstruction processing +#define TRACE_MICRO 0 + +// Log interrupts +#define TRACE_INT 0 + +// Log interrupts (detailed phases) +#define TRACE_INTD 0 + +// Log clock pulses +#define TRACE_CLOCK 0 + +// Log READY line input +#define TRACE_READY 0 + +// Log memory access +#define TRACE_MEM 0 + +// Log address bus operation +#define TRACE_ADDRESSBUS 0 + +// Log CRU operations +#define TRACE_CRU 0 + +// Log status register +#define TRACE_STATUS 0 + +// Log operation +#define TRACE_OP 0 + +// Log decrementer operation +#define TRACE_DEC 0 + +// Log with max detail +#define TRACE_DETAIL 0 + +/**************************************************************************** + Constructor +****************************************************************************/ + +tms9995_device::tms9995_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : cpu_device(mconfig, TMS9995, "TMS9995", tag, owner, clock, "tms9995", __FILE__), + m_state_any(0), + PC(0), + PC_debug(0), + m_program_config("program", ENDIANNESS_BIG, 8, 16), + m_io_config("cru", ENDIANNESS_BIG, 8, 16), + m_prgspace(NULL), + m_cru(NULL), + m_external_operation(*this), + m_iaq_line(*this), + m_clock_out_line(*this), + m_holda_line(*this), + m_dbin_line(*this) +{ + m_mp9537 = false; + m_check_overflow = false; +} + +/* + Called from subclass. +*/ +tms9995_device::tms9995_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source) + : cpu_device(mconfig, TMS9995, name, tag, owner, clock, shortname, source), + m_state_any(0), + PC(0), + PC_debug(0), + m_program_config("program", ENDIANNESS_BIG, 8, 16), + m_io_config("cru", ENDIANNESS_BIG, 8, 16), + m_prgspace(NULL), + m_cru(NULL), + m_external_operation(*this), + m_iaq_line(*this), + m_clock_out_line(*this), + m_holda_line(*this), + m_dbin_line(*this) +{ + m_check_overflow = false; +} + + +enum +{ + TMS9995_PC=0, TMS9995_WP, TMS9995_STATUS, TMS9995_IR, + TMS9995_R0, TMS9995_R1, TMS9995_R2, TMS9995_R3, + TMS9995_R4, TMS9995_R5, TMS9995_R6, TMS9995_R7, + TMS9995_R8, TMS9995_R9, TMS9995_R10, TMS9995_R11, + TMS9995_R12, TMS9995_R13, TMS9995_R14, TMS9995_R15 +}; + +void tms9995_device::device_start() +{ + // TODO: Restore save state suport + + m_prgspace = &space(AS_PROGRAM); // dimemory.h + m_cru = &space(AS_IO); + + // Resolve our external connections + m_external_operation.resolve(); + m_iaq_line.resolve(); + m_clock_out_line.resolve(); + m_holda_line.resolve(); + m_dbin_line.resolve(); + + // set our instruction counter + m_icountptr = &m_icount; + + // Clear the interrupt flags + m_int_pending = 0; + + m_mid_flag = false; + m_mid_active = false; + m_nmi_active = false; + m_int_overflow = false; + m_int_decrementer = false; + + m_idle_state = false; + + m_source_value = 0; + + // add the states for the debugger + for (int i=0; i < 20; i++) + { + // callimport = need to use the state_import method to write to the state variable + // callexport = need to use the state_export method to read the state variable + state_add(i, s_statename[i], m_state_any).callimport().callexport().formatstr("%04X"); + } + state_add(STATE_GENPC, "curpc", PC_debug).formatstr("%4s").noshow(); + state_add(STATE_GENFLAGS, "status", m_state_any).callimport().callexport().formatstr("%16s").noshow(); + + // Set up the lookup table for command decoding + build_command_lookup_table(); + + if (TRACE_CONFIG) logerror("%s: Variant = %s, Overflow int = %s\n", tag(), m_mp9537? "MP9537 (no on-chip RAM)" : "with on-chip RAM", m_check_overflow? "check" : "no check"); +} + +void tms9995_device::device_stop() +{ + int k = 0; + if (TRACE_CONFIG) logerror("%s: Deleting lookup tables\n", tag()); + while (m_lotables[k]!=NULL) delete[] m_lotables[k++]; +} + +/* + TMS9995 hard reset + The device reset is just the emulator's trigger for the reset procedure + which is invoked via the main loop. + + This also allows us to check the READY line at reset time, which is used + to enable automatic wait state creation. +*/ +void tms9995_device::device_reset() +{ + m_reset = true; // for the main loop + m_servicing_interrupt = false; // only for debugging + m_request_auto_wait_state = false; + memset(m_flag, 0, sizeof(m_flag)); +} + +const char* tms9995_device::s_statename[20] = +{ + "PC ", "WP ", "ST ", "IR ", + "R0 ", "R1 ", "R2 ", "R3 ", + "R4 ", "R5 ", "R6 ", "R7 ", + "R8 ", "R9 ", "R10", "R11", + "R12", "R13", "R14", "R15" +}; + +/* + Write the contents of a register by external input (debugger) + Note: this is untested any may fail because of the prefetch feature of the CPU. + In particular it may be required to adjust the PC. +*/ +void tms9995_device::state_import(const device_state_entry &entry) +{ + int index = entry.index(); + switch (entry.index()) + { + case STATE_GENFLAGS: + // no action here; we do not allow import, as the flags are all + // bits of the STATUS register + break; + case TMS9995_PC: + PC = (UINT16)m_state_any & 0xfffe; + break; + case TMS9995_WP: + WP = (UINT16)m_state_any & 0xfffe; + break; + case TMS9995_STATUS: + ST = (UINT16)m_state_any; + break; + case TMS9995_IR: + m_instruction->IR = (UINT16)m_state_any; + break; + default: + // Workspace registers + if (index <= TMS9995_R15) + write_workspace_register_debug(index-TMS9995_R0, (UINT16)m_state_any); + break; + } +} + +/* + Reads the contents of a register for display in the debugger. +*/ +void tms9995_device::state_export(const device_state_entry &entry) +{ + int index = entry.index(); + switch (entry.index()) + { + case STATE_GENFLAGS: + m_state_any = ST; + break; + case TMS9995_PC: + m_state_any = PC_debug; + break; + case TMS9995_WP: + m_state_any = WP; + break; + case TMS9995_STATUS: + m_state_any = ST; + break; + case TMS9995_IR: + m_state_any = m_instruction->IR; + break; + default: + // Workspace registers + if (index <= TMS9995_R15) + m_state_any = read_workspace_register_debug(index-TMS9995_R0); + break; + } +} + +/* + state_string_export - export state as a string for the debugger +*/ +void tms9995_device::state_string_export(const device_state_entry &entry, std::string &str) +{ + static const char *statestr = "LAECOPX-----IIII"; + char flags[17]; + memset(flags, 0x00, ARRAY_LENGTH(flags)); + UINT16 val = 0x8000; + if (entry.index()==STATE_GENFLAGS) + { + for (int i=0; i < 16; i++) + { + flags[i] = ((val & ST)!=0)? statestr[i] : '.'; + val = (val >> 1) & 0x7fff; + } + } + str.assign(flags); +} + +/* + Provide access to the workspace registers via the debugger. We have to + take care whether this is in onchip RAM or outside. +*/ +UINT16 tms9995_device::read_workspace_register_debug(int reg) +{ + int temp = m_icount; + UINT16 value; + + int addrb = (WP + (reg << 1)) & 0xfffe; + + if (is_onchip(addrb)) + { + value = (m_onchip_memory[addrb & 0x00fe]<<8) | m_onchip_memory[(addrb & 0x00fe) + 1]; + } + else + { + m_prgspace->set_debugger_access(true); + value = (m_prgspace->read_byte(addrb) << 8) & 0xff00; + value |= m_prgspace->read_byte(addrb+1); + m_prgspace->set_debugger_access(false); + } + m_icount = temp; + return value; +} + +void tms9995_device::write_workspace_register_debug(int reg, UINT16 data) +{ + int temp = m_icount; + int addrb = (WP + (reg << 1)) & 0xfffe; + + if (is_onchip(addrb)) + { + m_onchip_memory[addrb & 0x00fe] = (data >> 8) & 0xff; + m_onchip_memory[(addrb & 0x00fe) + 1] = data & 0xff; + } + else + { + m_prgspace->set_debugger_access(true); + m_prgspace->write_byte(addrb, (data >> 8) & 0xff); + m_prgspace->write_byte(addrb+1, data & 0xff); + m_prgspace->set_debugger_access(false); + } + m_icount = temp; +} + +const address_space_config *tms9995_device::memory_space_config(address_spacenum spacenum) const +{ + switch (spacenum) + { + case AS_PROGRAM: + return &m_program_config; + + case AS_IO: + return &m_io_config; + + default: + return NULL; + } +} + +/************************************************************************** + Microprograms for the CPU instructions + + The actions which are specific to the respective instruction are + invoked by repeated calls of ALU_xxx; each call increases a state + variable so that on the next call, the next part can be processed. + This saves us a lot of additional functions. +**************************************************************************/ + +/* + Define the indices for the micro-operation table. This is done for the sake + of a simpler microprogram definition as an UINT8[]. +*/ +enum +{ + PREFETCH, + PREFETCH_NO_INT, + MEMORY_READ, + MEMORY_WRITE, + WORD_READ, + WORD_WRITE, + OPERAND_ADDR, + INCREG, + INDX, + SET_IMM, + RETADDR, + RETADDR1, + CRU_INPUT, + CRU_OUTPUT, + ABORT, + END, + + ALU_NOP, + ALU_ADD_S_SXC, + ALU_B, + ALU_BLWP, + ALU_C, + ALU_CI, + ALU_CLR_SETO, + ALU_DIV, + ALU_DIVS, + ALU_EXTERNAL, + ALU_F3, + ALU_IMM_ARITHM, + ALU_JUMP, + ALU_LDCR, + ALU_LI, + ALU_LIMIWP, + ALU_LSTWP, + ALU_MOV, + ALU_MPY, + ALU_RTWP, + ALU_SBO_SBZ, + ALU_SHIFT, + ALU_SINGLE_ARITHM, + ALU_STCR, + ALU_STSTWP, + ALU_TB, + ALU_X, + ALU_XOP, + ALU_INT +}; + +#define MICROPROGRAM(_MP) \ + static const UINT8 _MP[] = + +/* + Cycles: + XXXX 1 => needs one cycle + xxxx 1 (1) => needs one cycle when accessing internal memory, two for external mem + PREFETCH 0 (1) => occurs during the last step in parallel, needs one more when fetching from outside + DECODE not shown here; assumed to happen during the next memory cycle; if there is none, + add another cycle + + OPERAND_ADDR x => needs x cycles for address derivation; see the separate table + + Prefetch always needs 1 or 2 cycles; the previous command occurs in parallel + to the prefetch, so we assign a 0 to the previous microprogram step +*/ + +MICROPROGRAM(operand_address_derivation) +{ + RETADDR, 0, 0, 0, // Register direct 0 + WORD_READ, RETADDR, 0, 0, // Register indirect 1 (1) + WORD_READ, RETADDR, 0, 0, // Symbolic 1 (1) + WORD_READ, INCREG, WORD_WRITE, RETADDR1, // Reg indirect auto-increment 3 (1) (1) + WORD_READ, INDX, WORD_READ, RETADDR // Indexed 3 (1) (1) +}; + +MICROPROGRAM(add_s_sxc_mp) +{ + OPERAND_ADDR, // x + MEMORY_READ, // 1 (1) + OPERAND_ADDR, // y + MEMORY_READ, // 1 (1) + ALU_ADD_S_SXC, // 0 (see above, occurs in parallel with PREFETCH) + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + decode in parallel (0) + END +}; + +MICROPROGRAM(b_mp) +{ + OPERAND_ADDR, // x + ALU_NOP, // 1 Don't read, just use the address + ALU_B, // 0 + PREFETCH, // 1 (1) + ALU_NOP, // 1 Don't save the return address + END +}; + +MICROPROGRAM(bl_mp) +{ + OPERAND_ADDR, // x + ALU_NOP, // 1 Don't read, just use the address + ALU_B, // 0 Re-use the alu operation from B + PREFETCH, // 1 (1) + ALU_NOP, // 1 + MEMORY_WRITE, // 1 (1) Write R11 + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(blwp_mp) +{ + OPERAND_ADDR, // x Determine source address + MEMORY_READ, // 1 (1) + ALU_BLWP, // 1 Got new WP, save it; increase address, save + MEMORY_WRITE, // 1 (1) save old ST to new R15 + ALU_BLWP, // 1 + MEMORY_WRITE, // 1 (1) save old PC to new R14 + ALU_BLWP, // 1 + MEMORY_WRITE, // 1 (1) save old WP to new R13 + ALU_BLWP, // 1 retrieve address + MEMORY_READ, // 1 (1) Read new PC + ALU_BLWP, // 0 Set new PC + PREFETCH, // 1 (1) + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(c_mp) +{ + OPERAND_ADDR, // x + MEMORY_READ, // 1 (1) + OPERAND_ADDR, // y + MEMORY_READ, // 1 (1) + ALU_C, // 0 + PREFETCH, // 1 (1) + ALU_NOP, // 1 decode + END +}; + +MICROPROGRAM(ci_mp) +{ + MEMORY_READ, // 1 (1) (reg) + SET_IMM, // 0 belongs to next cycle + MEMORY_READ, // 1 (1) (imm) + ALU_CI, // 0 set status + PREFETCH, // 1 (1) + ALU_NOP, // 1 decode + END +}; + +MICROPROGRAM(coc_czc_mp) +{ + OPERAND_ADDR, // x + MEMORY_READ, // 1 (1) + ALU_F3, // 0 + MEMORY_READ, // 1 (1) + ALU_F3, // 0 + PREFETCH, // 1 (1) + ALU_NOP, // 1 decode + END +}; + +MICROPROGRAM(clr_seto_mp) +{ + OPERAND_ADDR, // x + ALU_NOP, // 1 + ALU_CLR_SETO, // 0 + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + END +}; + +MICROPROGRAM(divide_mp) // TODO: Verify cycles on the real machine +{ + OPERAND_ADDR, // x Address of divisor S in Q=W1W2/S + MEMORY_READ, // 1 (1) Get S + ALU_DIV, // 1 + MEMORY_READ, // 1 (1) Get W1 + ALU_DIV, // 1 Check for overflow; skip next instruction if not + ABORT, // 1 + MEMORY_READ, // 1 (1) Get W2 + ALU_DIV, // d Calculate quotient + MEMORY_WRITE, // 1 (1) Write quotient to &W1 + ALU_DIV, // 0 + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) Write remainder to &W2 + END +}; + +MICROPROGRAM(divide_signed_mp) // TODO: Verify cycles on the real machine +{ + OPERAND_ADDR, // x Address of divisor S in Q=W1W2/S + MEMORY_READ, // 1 (1) Get S + ALU_DIVS, // 1 + MEMORY_READ, // 1 (1) Get W1 + ALU_DIVS, // 1 + MEMORY_READ, // 1 (1) Get W2 + ALU_DIVS, // 1 Check for overflow, skip next instruction if not + ABORT, // 1 + ALU_DIVS, // d Calculate quotient + MEMORY_WRITE, // 1 (1) Write quotient to &W1 + ALU_DIVS, // 0 + PREFETCH, // 1 + MEMORY_WRITE, // 1 (1) Write remainder to &W2 + END +}; + +MICROPROGRAM(external_mp) +{ + ALU_NOP, // 1 + ALU_NOP, // 1 + ALU_NOP, // 1 + ALU_NOP, // 1 + ALU_NOP, // 1 + ALU_EXTERNAL, // 0 + PREFETCH, // 1 (1) + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(imm_arithm_mp) +{ + MEMORY_READ, // 1 (1) + SET_IMM, // 0 + MEMORY_READ, // 1 (1) + ALU_IMM_ARITHM, // 0 + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + END +}; + +MICROPROGRAM(jump_mp) +{ + ALU_NOP, // 1 + ALU_JUMP, // 0 + PREFETCH, // 1 (1) + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(ldcr_mp) // TODO: Verify cycles +{ + ALU_LDCR, // 1 + OPERAND_ADDR, // x + MEMORY_READ, // 1 (1) Get source data + ALU_LDCR, // 1 Save it, point to R12 + WORD_READ, // 1 (1) Get R12 + ALU_LDCR, // 1 Prepare CRU operation + CRU_OUTPUT, // c + ALU_NOP, // 0 + PREFETCH, // 1 (1) + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(li_mp) +{ + SET_IMM, // 0 + MEMORY_READ, // 1 (1) + ALU_LI, // 0 + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + END +}; + +MICROPROGRAM(limi_lwpi_mp) +{ + SET_IMM, // 0 + MEMORY_READ, // 1 (1) + ALU_NOP, // 1 + ALU_LIMIWP, // 0 lwpi, 1 limi + PREFETCH, // 1 (1) + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(lst_lwp_mp) +{ + MEMORY_READ, // 1 (1) + ALU_NOP, // 1 + ALU_LSTWP, // 0 lwp, 1 lst + PREFETCH, // 1 (1) + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(mov_mp) +{ + OPERAND_ADDR, // 0 + MEMORY_READ, // 1 (1) + OPERAND_ADDR, // 0 + ALU_MOV, // 0 + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + END +}; + +MICROPROGRAM(multiply_mp) +{ + OPERAND_ADDR, // x + MEMORY_READ, // 1 (1) + ALU_MPY, // 1 + MEMORY_READ, // 1 (1) + ALU_MPY, // 17 + MEMORY_WRITE, // 1 (1) + ALU_MPY, // 0 + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + END +}; + +MICROPROGRAM(rtwp_mp) +{ + ALU_RTWP, // 1 + MEMORY_READ, // 1 (1) + ALU_RTWP, // 0 + MEMORY_READ, // 1 (1) + ALU_RTWP, // 0 + MEMORY_READ, // 1 (1) + ALU_RTWP, // 0 + PREFETCH, // 1 (1) + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(sbo_sbz_mp) +{ + ALU_SBO_SBZ, // 1 Set address = &R12 + WORD_READ, // 1 (1) Read R12 + ALU_SBO_SBZ, // 1 Add offset + CRU_OUTPUT, // 1 output via CRU + PREFETCH, // 1 (1) + ALU_NOP, // 1 + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(shift_mp) +{ + MEMORY_READ, // 1 (1) + ALU_SHIFT, // 2 skip next operation if count != 0 + MEMORY_READ, // 1 (1) if count=0 we must read R0 + ALU_SHIFT, // c do the shift + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + END +}; + +MICROPROGRAM(single_arithm_mp) +{ + OPERAND_ADDR, // x + MEMORY_READ, // 1 (1) + ALU_SINGLE_ARITHM, // 0 + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + END +}; + +MICROPROGRAM(stcr_mp) // TODO: Verify on real machine +{ + ALU_STCR, // 1 Check for byte operation + OPERAND_ADDR, // x Source operand + ALU_STCR, // 1 Save, set R12 + WORD_READ, // 1 (1) Read R12 + ALU_STCR, // 1 + CRU_INPUT, // c + ALU_STCR, // 13 + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + END +}; + +MICROPROGRAM(stst_stwp_mp) +{ + ALU_STSTWP, // 0 + ALU_NOP, // 1 + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + END +}; + +MICROPROGRAM(tb_mp) +{ + ALU_TB, // 1 + WORD_READ, // 1 (1) + ALU_TB, // 1 + CRU_INPUT, // 2 + ALU_TB, // 0 + PREFETCH, // 1 (1) + ALU_NOP, // 1 + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(x_mp) +{ + OPERAND_ADDR, // x + MEMORY_READ, // 1 (1) + ALU_X, // 1 + END // should not be reached +}; + +MICROPROGRAM(xop_mp) +{ + OPERAND_ADDR, // x Determine source address + ALU_XOP, // 1 Save it; determine XOP number + MEMORY_READ, // 1 (1) Read new WP + ALU_XOP, // 1 + MEMORY_WRITE, // 1 (1) save source address to new R11 + ALU_XOP, // 1 + MEMORY_WRITE, // 1 (1) save old ST to new R15 + ALU_XOP, // 1 + MEMORY_WRITE, // 1 (1) save old PC to new R14 + ALU_XOP, // 1 + MEMORY_WRITE, // 1 (1) save old WP to new R13 + ALU_XOP, // 1 + MEMORY_READ, // 1 (1) Read new PC + ALU_XOP, // 0 set new PC, set X flag + PREFETCH, // 1 (1) + ALU_NOP, // 1 + ALU_NOP, // 1 + END +}; + +MICROPROGRAM(xor_mp) +{ + OPERAND_ADDR, // x + MEMORY_READ, // 1 (1) + ALU_F3, // 0 + MEMORY_READ, // 1 (1) + ALU_F3, // 0 + PREFETCH, // 1 (1) + MEMORY_WRITE, // 1 (1) + END +}; + +MICROPROGRAM(int_mp) +{ + ALU_INT, // 1 + MEMORY_READ, // 1 (1) + ALU_INT, // 2 + MEMORY_WRITE, // 1 (1) + ALU_INT, // 1 + MEMORY_WRITE, // 1 (1) + ALU_INT, // 1 + MEMORY_WRITE, // 1 (1) + ALU_INT, // 1 + MEMORY_READ, // 1 (1) + ALU_INT, // 0 + PREFETCH_NO_INT, // 1 (1) (prefetch happens in parallel to the previous operation) + ALU_NOP, // 1 (+decode in parallel; actually performed right after prefetch) + ALU_NOP, // 1 + END +}; + +const tms9995_device::ophandler tms9995_device::s_microoperation[] = +{ + &tms9995_device::int_prefetch_and_decode, + &tms9995_device::prefetch_and_decode, + &tms9995_device::mem_read, + &tms9995_device::mem_write, + &tms9995_device::word_read, + &tms9995_device::word_write, + &tms9995_device::operand_address_subprogram, + &tms9995_device::increment_register, + &tms9995_device::indexed_addressing, + &tms9995_device::set_immediate, + &tms9995_device::return_with_address, + &tms9995_device::return_with_address_copy, + &tms9995_device::cru_input_operation, + &tms9995_device::cru_output_operation, + &tms9995_device::abort_operation, + &tms9995_device::command_completed, + + &tms9995_device::alu_nop, + &tms9995_device::alu_add_s_sxc, + &tms9995_device::alu_b, + &tms9995_device::alu_blwp, + &tms9995_device::alu_c, + &tms9995_device::alu_ci, + &tms9995_device::alu_clr_seto, + &tms9995_device::alu_divide, + &tms9995_device::alu_divide_signed, + &tms9995_device::alu_external, + &tms9995_device::alu_f3, + &tms9995_device::alu_imm_arithm, + &tms9995_device::alu_jump, + &tms9995_device::alu_ldcr, + &tms9995_device::alu_li, + &tms9995_device::alu_limi_lwpi, + &tms9995_device::alu_lst_lwp, + &tms9995_device::alu_mov, + &tms9995_device::alu_multiply, + &tms9995_device::alu_rtwp, + &tms9995_device::alu_sbo_sbz, + &tms9995_device::alu_shift, + &tms9995_device::alu_single_arithm, + &tms9995_device::alu_stcr, + &tms9995_device::alu_stst_stwp, + &tms9995_device::alu_tb, + &tms9995_device::alu_x, + &tms9995_device::alu_xop, + &tms9995_device::alu_int +}; + +/***************************************************************************** + CPU instructions +*****************************************************************************/ + +/* + Available instructions + MID is not a real instruction but stands for an invalid operation which + triggers a "macro instruction detect" interrupt. Neither is INTR which + indicates an interrupt handling in progress. +*/ +enum +{ + MID=0, A, AB, ABS, AI, ANDI, B, BL, BLWP, C, + CB, CI, CKOF, CKON, CLR, COC, CZC, DEC, DECT, DIV, + DIVS, IDLE, INC, INCT, INV, JEQ, JGT, JH, JHE, JL, + JLE, JLT, JMP, JNC, JNE, JNO, JOC, JOP, LDCR, LI, + LIMI, LREX, LST, LWP, LWPI, MOV, MOVB, MPY, MPYS, NEG, + ORI, RSET, RTWP, S, SB, SBO, SBZ, SETO, SLA, SOC, + SOCB, SRA, SRC, SRL, STCR, STST, STWP, SWPB, SZC, SZCB, + TB, X, XOP, XOR, INTR +}; + +static const char opname[][5] = +{ "MID ", "A ", "AB ", "ABS ", "AI ", "ANDI", "B ", "BL ", "BLWP", "C ", + "CB ", "CI ", "CKOF", "CKON", "CLR ", "COC ", "CZC ", "DEC ", "DECT", "DIV ", + "DIVS", "IDLE", "INC ", "INCT", "INV ", "JEQ ", "JGT ", "JH ", "JHE ", "JL ", + "JLE ", "JLT ", "JMP ", "JNC ", "JNE ", "JNO ", "JOC ", "JOP ", "LDCR", "LI ", + "LIMI", "LREX", "LST ", "LWP ", "LWPI", "MOV ", "MOVB", "MPY ", "MPYS", "NEG ", + "ORI ", "RSET", "RTWP", "S ", "SB ", "SBO ", "SBZ ", "SETO", "SLA ", "SOC ", + "SOCB", "SRA ", "SRC ", "SRL ", "STCR", "STST", "STWP", "SWPB", "SZC ", "SZCB", + "TB ", "X ", "XOP ", "XOR ", "*int" +}; + +/* + Formats: + + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + ----+------------------------------------------------+ + 1 | Opcode | B | Td | RegNr | Ts | RegNr | + +--------+---+----+------------+----+------------+ + 2 | Opcode | Displacement | + +-----------------------+------------------------+ + 3 | Opcode | RegNr | Ts | RegNr | + +-----------------+------------+----+------------+ + 4 | Opcode | Count | Ts | RegNr | + +-----------------+------------+----+------------+ + 5 | Opcode | Count | RegNr | + +-----------------------+-----------+------------+ + 6 | Opcode | Ts | RegNr | + +------------------------------+----+------------+ + 7 | Opcode |0| 0| 0| 0| 0 | + +---------------------------------+-+--+--+--+---+ + 8 | Opcode |0| RegNr | + +---------------------------------+-+------------+ + 9 | Opcode | Reg/Nr | Ts | RegNr | + +-----------------+------------+----+------------+ + 10 | Opcode | Ts | RegNr | (DIVS, MPYS) + +------------------------------+----+------------+ + 11 | Opcode | RegNr | (LST, LWP) + +-----------------------------------+------------+ +*/ + +/* + Defines the number of bits from the left which are significant for the + command in the respective format. +*/ +static const int format_mask_len[] = +{ + 0, 4, 8, 6, 6, 8, 10, 16, 12, 6, 10, 12 +}; + +const tms9995_device::tms_instruction tms9995_device::s_command[] = +{ + // Base opcode list + // Opcode, ID, format, microprg + { 0x0080, LST, 11, lst_lwp_mp }, + { 0x0090, LWP, 11, lst_lwp_mp }, + { 0x0180, DIVS, 10, divide_signed_mp }, + { 0x01C0, MPYS, 10, multiply_mp }, + { 0x0200, LI, 8, li_mp }, + { 0x0220, AI, 8, imm_arithm_mp }, + { 0x0240, ANDI, 8, imm_arithm_mp }, + { 0x0260, ORI, 8, imm_arithm_mp }, + { 0x0280, CI, 8, ci_mp }, + { 0x02a0, STWP, 8, stst_stwp_mp }, + { 0x02c0, STST, 8, stst_stwp_mp }, + { 0x02e0, LWPI, 8, limi_lwpi_mp }, + { 0x0300, LIMI, 8, limi_lwpi_mp }, + { 0x0340, IDLE, 7, external_mp }, + { 0x0360, RSET, 7, external_mp }, + { 0x0380, RTWP, 7, rtwp_mp }, + { 0x03a0, CKON, 7, external_mp }, + { 0x03c0, CKOF, 7, external_mp }, + { 0x03e0, LREX, 7, external_mp }, + { 0x0400, BLWP, 6, blwp_mp }, + { 0x0440, B, 6, b_mp }, + { 0x0480, X, 6, x_mp }, + { 0x04c0, CLR, 6, clr_seto_mp }, + { 0x0500, NEG, 6, single_arithm_mp }, + { 0x0540, INV, 6, single_arithm_mp }, + { 0x0580, INC, 6, single_arithm_mp }, + { 0x05c0, INCT, 6, single_arithm_mp }, + { 0x0600, DEC, 6, single_arithm_mp }, + { 0x0640, DECT, 6, single_arithm_mp }, + { 0x0680, BL, 6, bl_mp }, + { 0x06c0, SWPB, 6, single_arithm_mp }, + { 0x0700, SETO, 6, clr_seto_mp }, + { 0x0740, ABS, 6, single_arithm_mp }, + { 0x0800, SRA, 5, shift_mp }, + { 0x0900, SRL, 5, shift_mp }, + { 0x0a00, SLA, 5, shift_mp }, + { 0x0b00, SRC, 5, shift_mp }, + { 0x1000, JMP, 2, jump_mp }, + { 0x1100, JLT, 2, jump_mp }, + { 0x1200, JLE, 2, jump_mp }, + { 0x1300, JEQ, 2, jump_mp }, + { 0x1400, JHE, 2, jump_mp }, + { 0x1500, JGT, 2, jump_mp }, + { 0x1600, JNE, 2, jump_mp }, + { 0x1700, JNC, 2, jump_mp }, + { 0x1800, JOC, 2, jump_mp }, + { 0x1900, JNO, 2, jump_mp }, + { 0x1a00, JL, 2, jump_mp }, + { 0x1b00, JH, 2, jump_mp }, + { 0x1c00, JOP, 2, jump_mp }, + { 0x1d00, SBO, 2, sbo_sbz_mp }, + { 0x1e00, SBZ, 2, sbo_sbz_mp }, + { 0x1f00, TB, 2, tb_mp }, + { 0x2000, COC, 3, coc_czc_mp }, + { 0x2400, CZC, 3, coc_czc_mp }, + { 0x2800, XOR, 3, xor_mp }, + { 0x2c00, XOP, 3, xop_mp }, + { 0x3000, LDCR, 4, ldcr_mp }, + { 0x3400, STCR, 4, stcr_mp }, + { 0x3800, MPY, 9, multiply_mp }, + { 0x3c00, DIV, 9, divide_mp }, + { 0x4000, SZC, 1, add_s_sxc_mp }, + { 0x5000, SZCB, 1, add_s_sxc_mp }, + { 0x6000, S, 1, add_s_sxc_mp }, + { 0x7000, SB, 1, add_s_sxc_mp }, + { 0x8000, C, 1, c_mp }, + { 0x9000, CB, 1, c_mp }, + { 0xa000, A, 1, add_s_sxc_mp }, + { 0xb000, AB, 1, add_s_sxc_mp }, + { 0xc000, MOV, 1, mov_mp }, + { 0xd000, MOVB, 1, mov_mp }, + { 0xe000, SOC, 1, add_s_sxc_mp }, + { 0xf000, SOCB, 1, add_s_sxc_mp } +}; + +/* + Create a B-tree for looking up the commands. Each node can carry up to + 16 entries, indexed by 4 consecutive bits in the opcode. + + See tms9900.c for a detailed description. +*/ +void tms9995_device::build_command_lookup_table() +{ + int i = 0; + int cmdindex = 0; + int bitcount; + const tms_instruction *inst; + UINT16 opcode; + int k = 0; + + m_command_lookup_table = new lookup_entry[16]; + // We use lotables as a list of allocated tables - to be able to delete them + // at the end. + m_lotables[k++] = m_command_lookup_table; + + lookup_entry* table = m_command_lookup_table; + for (int j=0; j < 16; j++) + { + table[j].entry = NULL; + table[j].next_digit = NULL; + } + + do + { + inst = &s_command[i]; + table = m_command_lookup_table; + if (TRACE_EMU) logerror("tms9995: === opcode=%04x, len=%d\n", inst->opcode, format_mask_len[inst->format]); + bitcount = 4; + opcode = inst->opcode; + cmdindex = (opcode>>12) & 0x000f; + + while (bitcount < format_mask_len[inst->format]) + { + // Descend + if (table[cmdindex].next_digit == NULL) + { + if (TRACE_EMU) logerror("tms9995: create new table at bitcount=%d for index=%d\n", bitcount, cmdindex); + table[cmdindex].next_digit = new lookup_entry[16]; + m_lotables[k++] = table[cmdindex].next_digit; + for (int j=0; j < 16; j++) + { + table[cmdindex].next_digit[j].next_digit = NULL; + table[cmdindex].next_digit[j].entry = NULL; + } + } + else + { + if (TRACE_EMU) logerror("tms9995: found a table at bitcount=%d\n", bitcount); + } + + table = table[cmdindex].next_digit; + + bitcount = bitcount+4; + opcode <<= 4; + cmdindex = (opcode>>12) & 0x000f; + if (TRACE_EMU) logerror("tms9995: next index=%x\n", cmdindex); + } + + if (TRACE_EMU) logerror("tms9995: bitcount=%d\n", bitcount); + // We are at the target level + // Need to fill in the same entry for all values in the bitcount + // (if a command needs 10 bits we have to copy it four + // times for all combinations with 12 bits) + for (int j=0; j < (1<<(bitcount-format_mask_len[inst->format])); j++) + { + if (TRACE_EMU) logerror("tms9995: opcode=%04x at position %d\n", inst->opcode, cmdindex+j); + table[cmdindex+j].entry = inst; + } + + i++; + } while (inst->opcode != 0xf000); + + m_lotables[k++] = NULL; + if (TRACE_EMU) logerror("tms9995: Allocated %d tables\n", k); +} + +/* + Main execution loop + + For each invocation of execute_run, a number of loop iterations has been + calculated before (m_icount). Each loop iteration is one clock cycle. + The loop must be executed for the number of times that corresponds to the + time until the next timer event. +*/ +void tms9995_device::execute_run() +{ + if (m_reset) service_interrupt(); + + if (TRACE_EMU) logerror("tms9995: calling execute_run for %d cycles\n", m_icount); + do + { + // Normal operation + if (m_check_ready && m_ready == false) + { + // We are in a wait state + if (TRACE_WAITHOLD) logerror("tms9995: wait state\n"); + // The clock output should be used to change the state of an outer + // device which operates the READY line + pulse_clock(1); + } + else + { + if (m_check_hold && m_hold_state) + { + set_hold_state(true); + if (TRACE_WAITHOLD) logerror("tms9995: hold state\n"); + pulse_clock(1); + } + else + { + set_hold_state(false); + + m_check_ready = false; + + if (TRACE_MICRO) logerror("tms9995: main loop, operation %s, MPC = %d\n", opname[m_instruction->command], MPC); + (this->*s_microoperation[m_instruction->program[MPC]])(); + + // For multi-pass operations where the MPC should not advance + // or when we have put in a new microprogram + m_pass--; + if (m_pass<=0) + { + m_pass = 1; + MPC++; + } + } + } + } while (m_icount>0 && !m_reset); + if (TRACE_EMU) logerror("tms9995: cycles expired; will return soon.\n"); +} + +/**************************************************************************/ + +/* + Interrupt input + output + m_nmi_state + m_irq_level + flag[2], flag[4] +*/ +void tms9995_device::execute_set_input(int irqline, int state) +{ + if (irqline==INT_9995_RESET && state==ASSERT_LINE) + { + m_reset = true; + } + else + { + if (irqline == INPUT_LINE_NMI) + { + m_nmi_active = (state==ASSERT_LINE); + if (TRACE_INT) logerror("tms9995: NMI interrupt line state=%d\n", state); + } + else + { + if (irqline == INT_9995_INT1) + { + m_int1_active = m_flag[2] = (state==ASSERT_LINE); + if (TRACE_INT) logerror("tms9995: Line INT1 state=%d\n", state); + } + else + { + if (irqline == INT_9995_INT4) + { + if (TRACE_INT) logerror("tms9995: Line INT4/EC state=%d\n", state); + if (m_flag[0]==false) + { + if (TRACE_INT) logerror("tms9995: set as interrupt\n"); + m_int4_active = m_flag[4] = (state==ASSERT_LINE); + } + else + { + if (TRACE_INT) logerror("tms9995: set as event count\n"); + trigger_decrementer(); + } + } + else + { + logerror("tms9995: Accessed invalid interrupt line %d\n", irqline); + } + } + } + } +} + +/* + Issue a pulse on the clock line. +*/ +void tms9995_device::pulse_clock(int count) +{ + for (int i=0; i < count; i++) + { + if (!m_clock_out_line.isnull()) m_clock_out_line(ASSERT_LINE); + m_ready = m_ready_bufd && !m_request_auto_wait_state; // get the latched READY state + if (!m_clock_out_line.isnull()) m_clock_out_line(CLEAR_LINE); + m_icount--; // This is the only location where we count down the cycles. + if (TRACE_CLOCK) + { + if (m_check_ready) logerror("tms9995: pulse_clock, READY=%d, auto_wait=%d\n", m_ready_bufd? 1:0, m_auto_wait? 1:0); + else logerror("tms9995: pulse_clock\n"); + } + m_request_auto_wait_state = false; + if (m_flag[0] == false && m_flag[1] == true) + { + // Section 2.3.1.2.2: "by decreasing the count in the Decrementing + // Register by one for each fourth CLKOUT cycle" + m_decrementer_clkdiv = (m_decrementer_clkdiv+1)%4; + if (m_decrementer_clkdiv==0) trigger_decrementer(); + } + } +} + +/* + Enter the hold state. +*/ +void tms9995_device::set_hold(int state) +{ + m_hold_state = (state==ASSERT_LINE); + if (TRACE_WAITHOLD) logerror("tms9995: set HOLD = %d\n", state); + if (!m_hold_state) + { + if (!m_holda_line.isnull()) m_holda_line(CLEAR_LINE); + } +} + +/* + Signal READY to the CPU. When cleared, the CPU enters wait states. This + becomes effective on a clock pulse. +*/ +void tms9995_device::set_ready(int state) +{ + m_ready_bufd = (state==ASSERT_LINE); + if (TRACE_READY) logerror("tms9995: set READY = %d\n", m_ready_bufd? 1 : 0); +} + +/* + When the divide operations fail, we get to this operation. +*/ +void tms9995_device::abort_operation() +{ + int_prefetch_and_decode(); // do not forget to prefetch + // And don't forget that prefetch is a 2-pass operation, so this method + // will be called a second time. Only when the lowbyte has been fetched, + // continue with the next step + if (m_mem_phase==1) command_completed(); +} + +/* + Enter or leave the hold state. We only operate the HOLDA line when there is a change. +*/ +inline void tms9995_device::set_hold_state(bool state) +{ + if (m_hold_state != state) + if (!m_holda_line.isnull()) m_holda_line(state? ASSERT_LINE : CLEAR_LINE); + m_hold_state = state; +} + +/* + Decode the instruction. This is done in parallel to other operations + so we just do it together with the prefetch. +*/ +void tms9995_device::decode(UINT16 inst) +{ + int index = 0; + lookup_entry* table = m_command_lookup_table; + UINT16 opcode = inst; + bool complete = false; + const tms_instruction *decoded; + + int dindex = (m_instindex==0)? 1:0; + + m_mid_active = false; + + while (!complete) + { + index = (opcode >> 12) & 0x000f; + if (TRACE_EMU) logerror("tms9995: Check next hex digit of instruction %x\n", index); + if (table[index].next_digit != NULL) + { + table = table[index].next_digit; + opcode = opcode << 4; + } + else complete = true; + } + decoded = table[index].entry; + if (decoded == NULL) + { + // not found + logerror("tms9995: Undefined opcode %04x at logical address %04x, will trigger MID\n", inst, PC); + m_decoded[dindex].IR = 0; + m_decoded[dindex].command = MID; + } + else + { + m_decoded[dindex].IR = inst; + m_decoded[dindex].command = decoded->id; + m_decoded[dindex].program = decoded->prog; + m_decoded[dindex].byteop = ((decoded->format == 1) && ((inst & 0x1000)!=0)); + m_decoded[dindex].state = 0; + if (TRACE_EMU) logerror("tms9995: Command decoded as id %d, %s, base opcode %04x\n", decoded->id, opname[decoded->id], decoded->opcode); + m_pass = 1; + } +} + +/* + Fetch the next instruction and check pending interrupts before. + Getting an instruction is a normal memory access (plus an asserted IAQ line), + so this is subject to wait state handling. We have to allow for a two-pass + handling. +*/ +void tms9995_device::int_prefetch_and_decode() +{ + bool check_int = (m_instruction->command != XOP && m_instruction->command != BLWP); + int intmask = ST & 0x000f; + + if (m_mem_phase == 1) + { + // Check interrupt lines + if (m_nmi_active) + { + if (TRACE_INT) logerror("tms9995: Checking interrupts ... NMI active\n"); + m_int_pending |= PENDING_NMI; + m_idle_state = false; + PC = (PC + 2) & 0xfffe; // we have not prefetched the next instruction + return; + } + else + { + m_int_pending = 0; + + if (check_int) + { + if (m_int1_active && intmask >= 1) m_int_pending |= PENDING_LEVEL1; + if (m_int_overflow && intmask >= 2) m_int_pending |= PENDING_OVERFLOW; + if (m_int_decrementer && intmask >= 3) m_int_pending |= PENDING_DECR; + if (m_int4_active && intmask >= 4) m_int_pending |= PENDING_LEVEL4; + } + + if (m_int_pending!=0) + { + if (m_idle_state) + { + m_idle_state = false; + if (TRACE_INT) logerror("tms9995: Interrupt occurred, terminate IDLE state\n"); + } + PC = PC + 2; // PC must be advanced (see flow chart), but no prefetch + if (TRACE_INT) logerror("tms9995: Interrupts pending; no prefetch; advance PC to %04x\n", PC); + return; + } + else + { + if (TRACE_INT) logerror("tms9995: Checking interrupts ... none pending\n"); + // No pending interrupts + if (m_idle_state) + { + if (TRACE_WAITHOLD) logerror("tms9995: IDLE state\n"); + // We are IDLE, stay in the loop and do not advance the PC + m_pass = 2; + pulse_clock(1); + return; + } + } + } + } + + // We reach this point in phase 1 if there is no interrupt and in all other phases + prefetch_and_decode(); +} + +/* + The actual prefetch operation, but without the interrupt check. This one is + needed when we complete the interrupt handling and need to get the next + instruction. According to the flow chart in [1], the prefetch after the + interrupt handling ignores other pending interrupts. +*/ +void tms9995_device::prefetch_and_decode() +{ + if (m_mem_phase==1) + { + // Fetch next instruction + // Save these values; they have been computed during the current instruction execution + m_address_copy = m_address; + m_value_copy = m_current_value; + if (!m_iaq_line.isnull()) m_iaq_line(ASSERT_LINE); + m_address = PC; + if (TRACE_OP) logerror("tms9995: **** Prefetching new instruction at %04x ****\n", PC); + } + + word_read(); // changes m_mem_phase + + if (m_mem_phase==1) + { + // We're back in phase 1, i.e. the whole prefetch is done + decode(m_current_value); // This is for free; in reality it is in parallel with the next memory operation + m_address = m_address_copy; // restore m_address + m_current_value = m_value_copy; // restore m_current_value + PC = (PC + 2) & 0xfffe; // advance PC + if (!m_iaq_line.isnull()) m_iaq_line(CLEAR_LINE); + if (TRACE_OP) logerror("tms9995: ++++ Prefetch done ++++\n"); + } +} + +/* + Used by the normal command completion as well as by the X operation. We + assume that we have a fully decoded operation which was previously + prefetched. +*/ +void tms9995_device::next_command() +{ + int next = (m_instindex==0)? 1:0; + + if (m_decoded[next].command == MID) + { + m_mid_flag = true; + m_mid_active = true; + service_interrupt(); + } + else + { + m_instindex = next; + m_instruction = &m_decoded[m_instindex]; + m_get_destination = false; + // This is a preset for opcodes which do not need an opcode address derivation + m_address = WP + ((m_instruction->IR & 0x000f)<<1); + MPC = -1; + if (TRACE_OP) logerror("tms9995: ===== Next operation %04x (%s) at %04x =====\n", m_instruction->IR, opname[m_instruction->command], PC-2); + + if (TRACE_EXEC) + { + if (m_servicing_interrupt) logerror("i%04x\n", PC-2); + else logerror("%04x\n", PC-2); + } + PC_debug = PC - 2; + debugger_instruction_hook(this, PC_debug); + m_first_cycle = m_icount; + } +} + +/* + End of command execution +*/ +void tms9995_device::command_completed() +{ + // Pseudo state at the end of the current instruction cycle sequence + if (TRACE_CYCLES) + { + logerror("tms9995: +++++ Instruction %04x (%s) completed", m_instruction->IR, opname[m_instruction->command]); + int cycles = m_first_cycle - m_icount; + // Avoid nonsense values due to expired and resumed main loop + if (cycles > 0 && cycles < 10000) logerror(", consumed %d cycles", cycles); + logerror(" +++++\n"); + } + + if (m_int_pending != 0) + { + service_interrupt(); + } + else + { + if ((ST & ST_OE)!=0 && (ST & ST_OV)!=0 && (ST & 0x000f)>2) + { + service_interrupt(); + } + else + { + next_command(); + } + } +} + +/* + Handle pending interrupts. +*/ +void tms9995_device::service_interrupt() +{ + int vectorpos = 0; + + if (m_reset) + { + vectorpos = 0; + m_intmask = 0; // clear interrupt mask + + m_nmi_state = false; + m_hold_state = false; + m_mem_phase = 1; + m_check_hold = false; + m_word_access = false; + m_int1_active = false; + m_int4_active = false; + m_decrementer_clkdiv = 0; + + m_pass = 0; + m_instindex = 0; + m_instruction = &m_decoded[m_instindex]; + + memset(m_flag, 0, sizeof(m_flag)); + + ST = 0; + + // The auto-wait state generation is turned on when the READY line is cleared + // on RESET. + m_auto_wait = !m_ready_bufd; + if (TRACE_CONFIG) logerror("tms9995: RESET; automatic wait state creation is %s\n", m_auto_wait? "enabled":"disabled"); + // We reset the READY flag, or the CPU will not start + m_ready_bufd = true; + } + else + { + if (m_mid_active) + { + vectorpos = 0x0008; + m_intmask = 0x0001; + PC = (PC + 2) & 0xfffe; + if (TRACE_INT) logerror("tms9995: ***** MID pending\n"); + m_mid_active = false; + } + else + { + if ((m_int_pending & PENDING_NMI)!=0) + { + vectorpos = 0xfffc; + m_int_pending &= ~PENDING_NMI; + m_intmask = 0; + if (TRACE_INT) logerror("tms9995: ***** NMI pending\n"); + } + else + { + if ((m_int_pending & PENDING_LEVEL1)!=0) + { + vectorpos = 0x0004; + m_int_pending &= ~PENDING_LEVEL1; + m_flag[2] = false; + m_intmask = 0; + if (TRACE_INT) logerror("tms9995: ***** INT1 pending\n"); + } + else + { + if ((m_int_pending & PENDING_OVERFLOW)!=0) + { + vectorpos = 0x0008; + m_int_pending &= ~PENDING_OVERFLOW; + m_intmask = 0x0001; + if (TRACE_INT) logerror("tms9995: ***** OVERFL pending\n"); + } + else + { + if ((m_int_pending & PENDING_DECR)!=0) + { + vectorpos = 0x000c; + m_intmask = 0x0002; + m_int_pending &= ~PENDING_DECR; + m_flag[3] = false; + m_int_decrementer = false; + if (TRACE_DEC) logerror("tms9995: ***** DECR pending\n"); + } + else + { + vectorpos = 0x0010; + m_intmask = 0x0003; + m_int_pending &= ~PENDING_LEVEL4; + m_flag[4] = false; + if (TRACE_INT) logerror("tms9995: ***** INT4 pending\n"); + } + } + } + } + } + } + + if (TRACE_INT) logerror("tms9995: ********* triggered an interrupt with vector %04x/%04x\n", vectorpos, vectorpos+2); + + // just for debugging purposes + m_servicing_interrupt = true; + + // The microinstructions will do the context switch + m_address = vectorpos; + m_instruction->program = int_mp; + m_instruction->state = 0; + m_instruction->byteop = false; + m_instruction->command = INTR; + m_pass = m_reset? 1 : 2; + m_from_reset = m_reset; + + if (m_reset) + { + m_instruction->IR = 0x0000; + m_reset = false; + } + MPC = 0; + m_first_cycle = m_icount; + m_check_ready = false; // set to default +} + +/* + Read memory. This method expects as input m_address, and delivers the value + in m_current_value. For a single byte read, the byte is put into the high byte. + This method uses the m_pass variable to achieve a two-pass handling for + getting the complete word (high byte, low byte). + + input: + m_address + m_lowbyte + output: + m_current_value + + m_address is unchanged + + Make sure that m_lowbyte is false on the first call. +*/ +void tms9995_device::mem_read() +{ + // First determine whether the memory is inside the CPU + // On-chip memory is F000 ... F0F9, F0FA-FFF9 = off-chip, FFFA/B = Decrementer + // FFFC-FFFF = NMI vector (on-chip) + // There is a variant of the TMS9995 with no on-chip RAM which was used + // for the TI-99/8 (9537). + + if ((m_address & 0xfffe)==0xfffa && !m_mp9537) + { + if (TRACE_DEC) logerror("tms9995: read decrementer\n"); + // Decrementer mapped into the address space + m_current_value = m_decrementer_value; + if (m_instruction->byteop) + { + if ((m_address & 1)!=1) m_current_value <<= 8; + m_current_value &= 0xff00; + } + pulse_clock(1); + return; + } + + if (is_onchip(m_address)) + { + // If we have a word access, we have to align the address + // This is the case for word operations and for certain phases of + // byte operations (e.g. when retrieving the index register) + if (m_word_access || !m_instruction->byteop) m_address &= 0xfffe; + + if (TRACE_MEM) logerror("tms9995: read onchip memory (single pass, address %04x)\n", m_address); + + // Ignore the READY state + m_check_ready = false; + // We put fffc-ffff back into the f000-f0ff area + m_current_value = m_onchip_memory[m_address & 0x00ff]<<8; + if (m_word_access || !m_instruction->byteop) + { + // We have a word operation; add the low byte right here (just 1 cycle) + m_current_value |= (m_onchip_memory[(m_address & 0x00ff)+1] & 0xff); + } + pulse_clock(1); + } + else + { + // This is an off-chip access + m_check_ready = true; + UINT8 value; + UINT16 address = m_address; + + switch (m_mem_phase) + { + case 1: + // Set address + // If this is a word access, 4 passes, else 2 passes + if (!m_dbin_line.isnull()) m_dbin_line(ASSERT_LINE); + if (m_word_access || !m_instruction->byteop) + { + m_pass = 4; + // For word accesses, we always start at the even address + address &= 0xfffe; + } + else m_pass = 2; + + m_check_hold = false; + if (TRACE_ADDRESSBUS) logerror("tms9995: set address bus %04x\n", m_address & ~1); + m_prgspace->set_address(address); + m_request_auto_wait_state = m_auto_wait; + pulse_clock(1); + break; + case 2: + // Sample the value on the data bus (high byte) + if (m_word_access || !m_instruction->byteop) address &= 0xfffe; + value = m_prgspace->read_byte(address); + if (TRACE_MEM) logerror("tms9995: memory read byte %04x -> %02x\n", m_address & ~1, value); + m_current_value = (value << 8) & 0xff00; + break; + case 3: + // Set address + 1 (unless byte command) + if (TRACE_ADDRESSBUS) logerror("tms9995: set address bus %04x\n", m_address | 1); + m_prgspace->set_address(m_address | 1); + pulse_clock(1); + break; + case 4: + // Read low byte + value = m_prgspace->read_byte(m_address | 1); + m_current_value |= value; + if (TRACE_MEM) logerror("tms9995: memory read byte %04x -> %02x, complete word = %04x\n", m_address | 1, value, m_current_value); + m_check_hold = true; + break; + } + + m_mem_phase = (m_mem_phase % 4) +1; + + // Reset to 1 when we are done + if (m_pass==1) m_mem_phase = 1; + } +} + +/* + Read a word. This is independent of the byte flag of the instruction. + We need this variant especially when we have to retrieve a register value + in indexed addressing within a byte-oriented operation. +*/ +inline void tms9995_device::word_read() +{ + m_word_access = true; + mem_read(); + m_word_access = false; +} + +/* + Write memory. This method expects as input m_address and m_current_value. + For a single byte write, the byte to be written is expected to be in the + high byte of m_current_value. + This method uses the m_pass variable to achieve a two-pass handling for + writing the complete word (high byte, low byte). + + input: + m_address + m_lowbyte + m_current_value + + output: + - + m_address is unchanged + + Make sure that m_lowbyte is false on the first call. +*/ +void tms9995_device::mem_write() +{ + if ((m_address & 0xfffe)==0xfffa && !m_mp9537) + { + if (m_instruction->byteop) + { + // According to [1], section 2.3.1.2.2: + // "The decrementer should always be accessed as a full word. [...] + // Writing a single byte to either of the bytes of the decrementer + // will result in the data byte being written into the byte specifically addressed + // and random bits being written into the other byte of the decrementer." + + // So we just don't care about the low byte. + if (m_address == 0xfffb) m_current_value >>= 8; + + // dito: "This also loads the Decrementing Register with the same count." + m_starting_count_storage_register = m_decrementer_value = m_current_value; + } + else + { + m_starting_count_storage_register = m_decrementer_value = m_current_value; + } + if (TRACE_DEC) logerror("tms9995: Setting decrementer to %04x, PC=%04x\n", m_current_value, PC); + pulse_clock(1); + return; + } + + if (is_onchip(m_address)) + { + // If we have a word access, we have to align the address + // This is the case for word operations and for certain phases of + // byte operations (e.g. when retrieving the index register) + if (m_word_access || !m_instruction->byteop) m_address &= 0xfffe; + + if (TRACE_MEM) logerror("tms9995: write to onchip memory (single pass, address %04x, value=%04x)\n", m_address, m_current_value); + m_check_ready = false; + m_onchip_memory[m_address & 0x00ff] = (m_current_value >> 8) & 0xff; + if (m_word_access || !m_instruction->byteop) + { + m_onchip_memory[(m_address & 0x00ff)+1] = m_current_value & 0xff; + } + pulse_clock(1); + } + else + { + // This is an off-chip access + m_check_ready = true; + UINT16 address = m_address; + switch (m_mem_phase) + { + case 1: + // Set address + // If this is a word access, 4 passes, else 2 passes + if (!m_dbin_line.isnull()) m_dbin_line(CLEAR_LINE); + + if (m_word_access || !m_instruction->byteop) + { + m_pass = 4; + address &= 0xfffe; + } + else m_pass = 2; + + m_check_hold = false; + if (TRACE_ADDRESSBUS) logerror("tms9995: set address bus %04x\n", address); + m_prgspace->set_address(address); + if (TRACE_MEM) logerror("tms9995: memory write byte %04x <- %02x\n", address, (m_current_value >> 8)&0xff); + m_prgspace->write_byte(address, (m_current_value >> 8)&0xff); + pulse_clock(1); + break; + + case 2: + // no action here, just wait for READY + break; + case 3: + // Set address + 1 (unless byte command) + if (TRACE_ADDRESSBUS) logerror("tms9995: set address bus %04x\n", m_address | 1); + m_prgspace->set_address(m_address | 1); + if (TRACE_MEM) logerror("tms9995: memory write byte %04x <- %02x\n", m_address | 1, m_current_value & 0xff); + m_prgspace->write_byte(m_address | 1, m_current_value & 0xff); + pulse_clock(1); + break; + case 4: + // no action here, just wait for READY + m_check_hold = true; + break; + } + + m_mem_phase = (m_mem_phase % 4) +1; + + // Reset to 1 when we are done + if (m_pass==1) m_mem_phase = 1; + } +} + +/* + Write a word. This is independent of the byte flag of the instruction. +*/ +inline void tms9995_device::word_write() +{ + m_word_access = true; + mem_write(); + m_word_access = false; +} + +/* + Returns from the operand address derivation. +*/ +void tms9995_device::return_with_address() +{ + // Return from operand address derivation + // The result should be in m_address + m_instruction->program = m_caller; + MPC = m_caller_MPC; // will be increased on return + m_address = m_current_value + m_address_add; + if (TRACE_DETAIL) logerror("tms9995: +++ return from operand address derivation +++\n"); + // no clock pulse +} + +/* + Returns from the operand address derivation, but using the saved address. + This is required when we use the auto-increment feature. +*/ +void tms9995_device::return_with_address_copy() +{ + // Return from operand address derivation + m_instruction->program = m_caller; + MPC = m_caller_MPC; // will be increased on return + m_address = m_address_saved; + if (TRACE_DETAIL) logerror("tms9995: +++ return from operand address derivation (auto inc) +++\n"); + // no clock pulse +} + +/* + CRU support code + See common explanations in tms9900.c + + The TMS9995 CRU address space is larger than the CRU space of the TMS9900: + 0000-fffe (even addresses) instead of 0000-1ffe. Unlike the TMS9900, the + 9995 uses the data bus lines D0-D2 to indicate external operations. + + Internal CRU locations (read/write) + ----------------------------------- + 1EE0 Flag 0 Decrementer as event counter + 1EE2 Flag 1 Decrementer enable + 1EE4 Flag 2 Level 1 interrupt present (read only, also set when interrupt mask disallows interrupts) + 1EE6 Flag 3 Level 3 interrupt present (see above) + 1EE8 Flag 4 Level 4 interrupt present (see above) + ... + 1EFE Flag 15 + 1FDA MID flag (only indication, does not trigger when set) + + The TMS9995 allows for wait states during external CRU access. Therefore + we read one block of 8 bits in one go (as given by the MESS architecture) + but we do iterations for each bit, checking every time for the READY line + in the main loop. + + (write) + m_cru_output + m_cru_address + m_cru_value + m_count + +*/ + +#define CRUREADMASK 0x0fff +#define CRUWRITEMASK 0x7fff + +void tms9995_device::cru_output_operation() +{ + if (TRACE_CRU) logerror("tms9995: CRU output operation, address %04x, value %d\n", m_cru_address, m_cru_value & 0x01); + + if (m_cru_address == 0x1fda) + { + // [1], section 2.3.3.2.2: "setting the MID flag to one with a CRU instruction + // will not cause the MID interrupt to be requested." + m_check_ready = false; + m_mid_flag = (m_cru_value & 0x01); + } + else + { + if ((m_cru_address & 0xffe0) == 0x1ee0) + { + m_check_ready = false; + // FLAG2, FLAG3, and FLAG4 are read-only + if (TRACE_CRU) logerror("tms9995: set CRU address %04x to %d\n", m_cru_address, m_cru_value&1); + if ((m_cru_address != 0x1ee4) && (m_cru_address != 0x1ee6) && (m_cru_address != 0x1ee8)) + m_flag[(m_cru_address>>1)&0x000f] = (m_cru_value & 0x01); + } + else + { + // External access + m_check_ready = true; + } + } + + // All CRU write operations are visible to the outside world, even when we + // have internal access. This makes it possible to assign special + // functions to the internal flag bits which are realized outside + // of the CPU. However, no wait states are generated for internal + // accesses. ([1], section 2.3.3.2) + + m_cru->write_byte((m_cru_address >> 1)& CRUWRITEMASK, (m_cru_value & 0x01)); + m_cru_value >>= 1; + m_cru_address = (m_cru_address + 2) & 0xfffe; + m_count--; + + // Repeat this operation + m_pass = (m_count > 0)? 2 : 1; + pulse_clock(2); +} + +/* + Input: (read) + m_cru_multi_first + m_cru_address + Output: + m_cru_value (right-shifted; i.e. first bit is LSB of the 16 bit word, + also for byte operations) +*/ + +void tms9995_device::cru_input_operation() +{ + UINT16 crubit; + UINT8 crubyte; + + // Reading is different, since MESS uses 8 bit transfers + // We read 8 bits in one go, then iterate another min(n-1,7) times to allow + // for wait states. + + // read_byte for CRU delivers the first bit on the rightmost position + + int offset = (m_cru_address>>1) & 0x07; + + if (m_cru_first_read || m_cru_bits_left == 0) + { + // Read next 8 bits + // 00000000 0rrrrrrr r + // v + // ........ ........ X....... ........ + // + crubyte = m_cru->read_byte((m_cru_address >> 4)& CRUREADMASK); + if (TRACE_DETAIL) logerror("tms9995: Need to get next 8 bits (addresses %04x-%04x): %02x\n", (m_cru_address&0xfff0)+14, m_cru_address&0xfff0, crubyte); + m_cru_read = crubyte << 15; + m_cru_bits_left = 8; + + if (m_cru_first_read) + { + m_cru_read >>= offset; + m_cru_bits_left -= offset; + m_parity = 0; + m_cru_value = 0; + m_cru_first_read = false; + m_pass = m_count; + } + if (TRACE_DETAIL) logerror("tms9995: adjusted value for shift: %06x\n", m_cru_read); + } + + crubit = (m_cru_read & 0x8000); + m_cru_value = (m_cru_value >> 1) & 0x7fff; + + // During internal reading, the CRUIN line will be ignored. We emulate this + // by overwriting the bit which we got from outside. Also, READY is ignored. + if (m_cru_address == 0x1fda) + { + crubit = m_mid_flag? 0x8000 : 0x0000; + m_check_ready = false; + } + else + { + if ((m_cru_address & 0xffe0)==0x1ee0) + { + crubit = (m_flag[(m_cru_address>>1)&0x000f]==true)? 0x8000 : 0x0000; + m_check_ready = false; + } + else + { + m_check_ready = true; + } + } + + if (TRACE_CRU) logerror("tms9995: CRU input operation, address %04x, value %d\n", m_cru_address, (crubit & 0x8000)>>15); + + m_cru_value |= crubit; + if (crubit!=0) m_parity++; + + m_cru_address = (m_cru_address + 2) & 0xfffe; + m_cru_bits_left--; + + if (m_pass > 1) + { + m_cru_read >>= 1; + } + else + { + // This is the final shift. For both byte and word length transfers, + // the first bit is always m_cru_value & 0x0001. + m_cru_value >>= (16 - m_count); + } + pulse_clock(2); +} + +/* + Decrementer. +*/ +void tms9995_device::trigger_decrementer() +{ + if (m_starting_count_storage_register>0) // null will turn off the decrementer + { + m_decrementer_value--; + if (m_decrementer_value==0) + { + if (TRACE_DEC) logerror("tms9995: decrementer reached 0\n"); + m_decrementer_value = m_starting_count_storage_register; + if (m_flag[1]==true) + { + if (TRACE_DEC) logerror("tms9995: decrementer flags interrupt\n"); + m_flag[3] = true; + m_int_decrementer = true; + } + } + } +} + +/* + This is a switch to a subprogram. In terms of cycles + it does not take any time; execution continues with the first instruction + of the subprogram. + + input: + m_get_destination + m_instruction + WP + m_current_value + m_address + output: + m_source_value = m_current_value before invocation + m_current_value = m_address + m_address_add = 0 + m_lowbyte = false + m_get_destination = true + m_regnumber = register number + m_address = address of register + */ +void tms9995_device::operand_address_subprogram() +{ + UINT16 ircopy = m_instruction->IR; + if (m_get_destination) ircopy = ircopy >> 6; + + // Save the return program and position + m_caller = m_instruction->program; + m_caller_MPC = MPC; + + m_instruction->program = (UINT8*)operand_address_derivation; + MPC = (ircopy & 0x0030) >> 2; + m_regnumber = (ircopy & 0x000f); + m_address = (WP + (m_regnumber<<1)) & 0xffff; + + m_source_value = m_current_value; // will be overwritten when reading the destination + m_current_value = m_address; // needed for first case + + if (MPC==8) // Symbolic + { + if (m_regnumber != 0) + { + if (TRACE_DETAIL) logerror("tms9995: indexed addressing\n"); + MPC = 16; // indexed + } + else + { + if (TRACE_DETAIL) logerror("tms9995: symbolic addressing\n"); + m_address = PC; + PC = (PC + 2) & 0xfffe; + } + } + + m_get_destination = true; + m_mem_phase = 1; + m_address_add = 0; + MPC--; // will be increased in the mail loop + if (TRACE_DETAIL) logerror("tms9995: *** Operand address derivation; address=%04x; index=%d\n", m_address, MPC+1); +} + +/* + Used for register auto-increment. We have to save the address read from the + register content so that we can return it at the end. +*/ +void tms9995_device::increment_register() +{ + m_address_saved = m_current_value; // need a special return so we do not lose the value + m_current_value += m_instruction->byteop? 1 : 2; + m_address = (WP + (m_regnumber<<1)) & 0xffff; + m_mem_phase = 1; + pulse_clock(1); +} + +/* + Used for indexed addressing. We store the contents of the index register + in m_address_add which is set to 0 by default. Then we set the address + pointer to the PC location and advance it. +*/ +void tms9995_device::indexed_addressing() +{ + m_address_add = m_current_value; + m_address = PC; + PC = (PC + 2) & 0xfffe; + m_mem_phase = 1; + pulse_clock(1); +} + +void tms9995_device::set_immediate() +{ + // Need to determine the register address + m_address_saved = WP + ((m_instruction->IR & 0x000f)<<1); + m_address = PC; + m_source_value = m_current_value; // needed for AI, ANDI, ORI + PC = (PC + 2) & 0xfffe; + m_mem_phase = 1; +} + +/************************************************************************** + Status bit operations +**************************************************************************/ + +inline void tms9995_device::set_status_bit(int bit, bool state) +{ + if (state) ST |= bit; + else ST &= ~bit; + m_int_overflow = (m_check_overflow && bit == ST_OV && ((ST & ST_OE)!=0) && state == true); +} + +void tms9995_device::set_status_parity(UINT8 value) +{ + int count = 0; + for (int i=0; i < 8; i++) + { + if ((value & 0x80)!=0) count++; + value <<= 1; + } + set_status_bit(ST_OP, (count & 1)!=0); +} + +inline void tms9995_device::compare_and_set_lae(UINT16 value1, UINT16 value2) +{ + set_status_bit(ST_EQ, value1 == value2); + set_status_bit(ST_LH, value1 > value2); + set_status_bit(ST_AGT, (INT16)value1 > (INT16)value2); +} + +/************************************************************************** + ALU operations. The activities as implemented here are performed + during the internal operations of the CPU, according to the current + instruction. + + Some ALU operations are followed by the prefetch operation. In fact, + this prefetch happens in parallel to the ALU operation. In these + situations we do not pulse the clock here but leave this to the prefetch + operation. +**************************************************************************/ + +void tms9995_device::alu_nop() +{ + // Do nothing (or nothing that is externally visible) + pulse_clock(1); + return; +} + +void tms9995_device::alu_add_s_sxc() +{ + // We have the source operand value in m_source_value and the destination + // value in m_current_value + // The destination address is still in m_address + // Prefetch will not change m_current_value and m_address + + UINT32 dest_new = 0; + + switch (m_instruction->command) + { + case A: + case AB: + // When adding, a carry occurs when we exceed the 0xffff value. + dest_new = m_current_value + m_source_value; + set_status_bit(ST_C, (dest_new & 0x10000) != 0); + + // If the result has a sign bit that is different from both arguments, we have an overflow + // (i.e. getting a negative value from two positive values and vice versa) + set_status_bit(ST_OV, ((dest_new ^ m_current_value) & (dest_new ^ m_source_value) & 0x8000)!=0); + break; + case S: + case SB: + dest_new = m_current_value + ((~m_source_value) & 0xffff) + 1; + // Subtraction means adding the 2s complement, so the carry bit + // is set whenever adding the 2s complement exceeds ffff + // In fact the CPU adds the one's complement, then adds a one. This + // explains why subtracting 0 sets the carry bit. + set_status_bit(ST_C, (dest_new & 0x10000) != 0); + + // If the arguments have different sign bits and the result has a + // sign bit different from the destination value, we have an overflow + // e.g. value1 = 0x7fff, value2 = 0xffff; value1-value2 = 0x8000 + // or value1 = 0x8000, value2 = 0x0001; value1-value2 = 0x7fff + // value1 is the destination value + set_status_bit(ST_OV, (m_current_value ^ m_source_value) & (m_current_value ^ dest_new) & 0x8000); + break; + case SOC: + case SOCB: + dest_new = m_current_value | m_source_value; + break; + + case SZC: + case SZCB: + dest_new = m_current_value & ~m_source_value; + break; + } + + m_current_value = (UINT16)(dest_new & 0xffff); + + compare_and_set_lae((UINT16)(dest_new & 0xffff),0); + if (m_instruction->byteop) + { + set_status_parity((UINT8)(dest_new>>8)); + } + if (TRACE_STATUS) logerror("tms9995: ST = %04x (val=%04x)\n", ST, m_current_value); + // No clock pulse (will be done by prefetch) +} + +/* + Branch / Branch and link. We put the source address into the PC after + copying the PC into m_current_value. The address is R11. The B instruction + will just ignore these settings, but BL will use them. +*/ +void tms9995_device::alu_b() +{ + m_current_value = PC; + PC = m_address & 0xfffe; + m_address = WP + 22; +} + +/* + Branch and load workspace pointer. This is a branch to a subprogram with + context switch. +*/ +void tms9995_device::alu_blwp() +{ + int n = 1; + switch (m_instruction->state) + { + case 0: + // new WP in m_current_value + m_value_copy = WP; + WP = m_current_value & 0xfffe; + m_address_saved = m_address + 2; + m_address = WP + 30; + m_current_value = ST; + break; + case 1: + m_current_value = PC; + m_address = m_address - 2; + break; + case 2: + m_current_value = m_value_copy; // old WP + m_address = m_address - 2; + break; + case 3: + m_address = m_address_saved; + break; + case 4: + PC = m_current_value & 0xfffe; + n = 0; + if (TRACE_OP) logerror("tms9995: Context switch complete; WP=%04x, PC=%04x, ST=%04x\n", WP, PC, ST); + break; + } + m_instruction->state++; + pulse_clock(n); +} + +/* + Compare is similar to add, s, soc, szc, but we do not write a result. +*/ +void tms9995_device::alu_c() +{ + // We have the source operand value in m_source_value and the destination + // value in m_current_value + // The destination address is still in m_address + // Prefetch will not change m_current_value and m_address + if (m_instruction->byteop) + { + set_status_parity((UINT8)(m_source_value>>8)); + } + compare_and_set_lae(m_source_value, m_current_value); + if (TRACE_STATUS) logerror("tms9995: ST = %04x (val1=%04x, val2=%04x)\n", ST, m_source_value, m_current_value); +} + +/* + Compare with immediate value. +*/ +void tms9995_device::alu_ci() +{ + // We have the register value in m_source_value, the register address in m_address_saved + // and the immediate value in m_current_value + compare_and_set_lae(m_source_value, m_current_value); + if (TRACE_STATUS) logerror("tms9995: ST = %04x (val1=%04x, val2=%04x)\n", ST, m_source_value, m_current_value); +} + +void tms9995_device::alu_clr_seto() +{ + if (TRACE_OP) logerror("tms9995: clr/seto: Setting values for address %04x\n", m_address); + switch (m_instruction->command) + { + case CLR: + m_current_value = 0; + break; + case SETO: + m_current_value = 0xffff; + break; + } + // No clock pulse, as next instruction is prefetch +} + +/* + Unsigned division. +*/ +void tms9995_device::alu_divide() +{ + int n=1; + UINT32 uval32; + + bool overflow = true; + UINT16 value1; + + switch (m_instruction->state) + { + case 0: + m_source_value = m_current_value; + // Set address of register + m_address = WP + ((m_instruction->IR >> 5) & 0x001e); + m_address_copy = m_address; + break; + case 1: + // Value of register is in m_current_value + // We have an overflow when the quotient cannot be stored in 16 bits + // This is the case when the dividend / divisor >= 0x10000, + // or equivalently, dividend / 0x10000 >= divisor + + // Check overflow for unsigned DIV + if (m_current_value < m_source_value) // also if source=0 + { + MPC++; // skip the abort + overflow = false; + } + set_status_bit(ST_OV, overflow); + m_value_copy = m_current_value; // Save the high word + m_address = m_address + 2; + break; + case 2: + // W2 is in m_current_value + uval32 = (m_value_copy << 16) | m_current_value; + // Calculate + // The number of ALU cycles depends on the number of steps in + // the division algorithm. The number of cycles is between 1 and 16 + // As in TMS9900, this is a guess; it depends on the actual algorithm + // used in the chip. + + m_current_value = uval32 / m_source_value; + m_value_copy = uval32 % m_source_value; + m_address = m_address_copy; + + value1 = m_value_copy & 0xffff; + while (value1 != 0) + { + value1 = (value1 >> 1) & 0xffff; + n++; + } + + break; + case 3: + // now write the remainder + m_current_value = m_value_copy; + m_address = m_address + 2; + break; + } + m_instruction->state++; + pulse_clock(n); +} + +/* + Signed Division + We cannot handle this by the same ALU operation because we can NOT decide + whether there is an overflow before we have retrieved the whole 32 bit + word. Also, the overflow detection is pretty complicated for signed + division when done before the actual calculation. +*/ +void tms9995_device::alu_divide_signed() +{ + int n=1; + bool overflow = true; + UINT16 w1, w2, dwait; + INT16 divisor; + INT32 dividend; + + switch (m_instruction->state) + { + case 0: + // Got the source value (divisor) + m_source_value = m_current_value; + m_address = WP; // DIVS always uses R0,R1 + break; + case 1: + // Value of register is in m_current_value + m_value_copy = m_current_value; + m_address += 2; + break; + case 2: + // Now we have the dividend low word in m_current_value, + // the dividend high word in m_value_copy, and + // the divisor in m_source_value. + + w1 = m_value_copy; + w2 = m_current_value; + divisor = m_source_value; + dividend = w1 << 16 | w2; + + // Now check for overflow + // We need to go for four cases + // if the divisor is not 0 anyway + if (divisor != 0) + { + if (dividend >= 0) + { + if (divisor > 0) + { + overflow = (dividend > ((divisor<<15) - 1)); + } + else + { + overflow = (dividend > (((-divisor)<<15) + (-divisor) - 1)); + } + } + else + { + if (divisor > 0) + { + overflow = ((-dividend) > ((divisor<<15) + divisor - 1)); + } + else + { + overflow = ((-dividend) > (((-divisor)<<15) - 1)); + } + } + } + else + { + overflow = true; // divisor is 0 + } + set_status_bit(ST_OV, overflow); + if (!overflow) MPC++; // Skip the next microinstruction when there is no overflow + break; + case 3: + // We are here because there was no overflow + dividend = m_value_copy << 16 | m_current_value; + // Do the calculation + m_current_value = (UINT16)(dividend / (INT16)m_source_value); + m_value_copy = (UINT16)(dividend % (INT16)m_source_value); + m_address = WP; + + // As we have not implemented the real division algorithm we must + // simulate the number of steps required for calculating the result. + // This is just a guess. + dwait = m_value_copy; + while (dwait != 0) + { + dwait = (dwait >> 1) & 0xffff; + n++; + } + // go write the quotient into R0 + break; + case 4: + // Now write the remainder + m_current_value = m_value_copy; + m_address += 2; + n = 0; + break; + } + m_instruction->state++; + pulse_clock(n); +} + +/* + External operations. +*/ +void tms9995_device::alu_external() +{ + // Call some possibly attached external device + // A specific bit pattern is put on the data bus, and the CRUOUT line is + // pulsed. In our case we use a special callback function since we cannot + // emulate this behavior in this implementation. + + // Opcodes D012 value + // -----------------vvv------ + // IDLE = 0000 0011 0100 0000 + // RSET = 0000 0011 0110 0000 + // CKON = 0000 0011 1010 0000 + // CKOF = 0000 0011 1100 0000 + // LREX = 0000 0011 1110 0000 + + // Only IDLE has a visible effect on the CPU without external support: the + // CPU will stop execution until an interrupt occurs. CKON, CKOF, LREX have + // no effect without external support. Neither has RSET, it does *not* + // cause a reset of the CPU or of the remaining computer system. + // It only clears the interrupt mask and outputs the + // external code on the data bus. A special line decoder could then trigger + // a reset from outside. + + if (m_instruction->command == IDLE) + { + if (TRACE_OP) logerror("tms9995: Entering IDLE state\n"); + m_idle_state = true; + } + + if (m_instruction->command == RSET) + { + ST &= 0xfff0; + if (TRACE_OP) logerror("tms9995: RSET, new ST = %04x\n", ST); + } + + if (!m_external_operation.isnull()) m_external_operation((m_instruction->IR >> 5) & 0x07, 1, 0xff); +} + +/* + Logical compare and XOR +*/ +void tms9995_device::alu_f3() +{ + switch (m_instruction->state) + { + case 0: + // We have the contents of the source in m_current_value and its address + // in m_address + m_source_value = m_current_value; + // Get register address + m_address = WP + ((m_instruction->IR >> 5) & 0x001e); + break; + case 1: + // Register contents -> m_current_value + // Source contents -> m_source_value + if (m_instruction->command == COC) + { + set_status_bit(ST_EQ, (m_current_value & m_source_value) == m_source_value); + } + else + { + if (m_instruction->command == CZC) + { + set_status_bit(ST_EQ, (~m_current_value & m_source_value) == m_source_value); + } + else + { + // XOR + // The workspace register address is still in m_address + m_current_value = (m_current_value ^ m_source_value); + compare_and_set_lae(m_current_value, 0); + } + } + if (TRACE_STATUS) logerror("tms9995: ST = %04x\n", ST); + break; + } + m_instruction->state++; +} + +/* + Handles AI, ANDI, ORI. +*/ +void tms9995_device::alu_imm_arithm() +{ + UINT32 dest_new = 0; + + // We have the register value in m_source_value, the register address in m_address_saved + // and the immediate value in m_current_value + switch (m_instruction->command) + { + case AI: + dest_new = m_current_value + m_source_value; + set_status_bit(ST_C, (dest_new & 0x10000) != 0); + + // If the result has a sign bit that is different from both arguments, we have an overflow + // (i.e. getting a negative value from two positive values and vice versa) + set_status_bit(ST_OV, ((dest_new ^ m_current_value) & (dest_new ^ m_source_value) & 0x8000)!=0); + break; + case ANDI: + dest_new = m_current_value & m_source_value; + break; + case ORI: + dest_new = m_current_value | m_source_value; + break; + } + + m_current_value = (UINT16)(dest_new & 0xffff); + compare_and_set_lae(m_current_value, 0); + m_address = m_address_saved; + if (TRACE_STATUS) logerror("tms9995: ST = %04x (val=%04x)\n", ST, m_current_value); +} + +/* + Handles all jump instructions. +*/ +void tms9995_device::alu_jump() +{ + bool cond = false; + INT8 displacement = (m_instruction->IR & 0xff); + + switch (m_instruction->command) + { + case JMP: + cond = true; + break; + case JLT: // LAECOP == x00xxx + cond = ((ST & (ST_AGT | ST_EQ))==0); + break; + case JLE: // LAECOP == 0xxxxx + cond = ((ST & ST_LH)==0); + break; + case JEQ: // LAECOP == xx1xxx + cond = ((ST & ST_EQ)!=0); + break; + case JHE: // LAECOP == 1x0xxx, 0x1xxx + cond = ((ST & (ST_LH | ST_EQ)) != 0); + break; + case JGT: // LAECOP == x1xxxx + cond = ((ST & ST_AGT)!=0); + break; + case JNE: // LAECOP == xx0xxx + cond = ((ST & ST_EQ)==0); + break; + case JNC: // LAECOP == xxx0xx + cond = ((ST & ST_C)==0); + break; + case JOC: // LAECOP == xxx1xx + cond = ((ST & ST_C)!=0); + break; + case JNO: // LAECOP == xxxx0x + cond = ((ST & ST_OV)==0); + break; + case JL: // LAECOP == 0x0xxx + cond = ((ST & (ST_LH | ST_EQ)) == 0); + break; + case JH: // LAECOP == 1xxxxx + cond = ((ST & ST_LH)!=0); + break; + case JOP: // LAECOP == xxxxx1 + cond = ((ST & ST_OP)!=0); + break; + } + + if (!cond) + { + if (TRACE_OP) logerror("tms9995: Jump condition false\n"); + } + else + { + if (TRACE_OP) logerror("tms9995: Jump condition true\n"); + PC = (PC + (displacement<<1)) & 0xfffe; + } +} + +/* + Implements LDCR. +*/ +void tms9995_device::alu_ldcr() +{ + switch (m_instruction->state) + { + case 0: + m_count = (m_instruction->IR >> 6) & 0x000f; + if (m_count==0) m_count = 16; + m_instruction->byteop = (m_count<9); + break; + case 1: + // We have read the byte or word into m_current_value. + compare_and_set_lae(m_current_value, 0); + if (TRACE_STATUS) logerror("tms9995: ST = %04x (val=%04x)\n", ST, m_current_value); + if (m_instruction->byteop) + { + m_current_value = (m_current_value>>8) & 0xff; + set_status_parity((UINT8)m_current_value); + } + m_cru_value = m_current_value; + m_address = WP + 24; + break; + case 2: + // Prepare CRU operation + m_cru_address = m_current_value; + break; + } + m_instruction->state++; + pulse_clock(1); +} + +/* + Implements LI. Almost everything has been done in the microprogram; + this part is reached with m_address_saved = register address, + and m_current_value = *m_address; +*/ +void tms9995_device::alu_li() +{ + // Retrieve the address of the register + // The immediate value is still in m_current_value + m_address = m_address_saved; + compare_and_set_lae(m_current_value, 0); + if (TRACE_STATUS) logerror("tms9995: ST = %04x (val=%04x)\n", ST, m_current_value); +} + +void tms9995_device::alu_limi_lwpi() +{ + // The immediate value is in m_current_value + if (m_instruction->command == LIMI) + { + ST = (ST & 0xfff0) | (m_current_value & 0x000f); + if (TRACE_OP) logerror("tms9995: LIMI sets ST = %04x\n", ST); + pulse_clock(1); // needs one more than LWPI + } + else + { + WP = m_current_value & 0xfffe; + if (TRACE_OP) logerror("tms9995: LWPI sets new WP = %04x\n", WP); + } +} + +/* + Load status and load workspace pointer. This is a TMS9995-specific + operation. +*/ +void tms9995_device::alu_lst_lwp() +{ + if (m_instruction->command==LST) + { + ST = m_current_value; + if (TRACE_OP) logerror("tms9995: new ST = %04x\n", ST); + pulse_clock(1); + } + else + { + WP = m_current_value & 0xfffe; + if (TRACE_OP) logerror("tms9995: new WP = %04x\n", WP); + } +} + +/* + The MOV operation on the TMS9995 is definitely more efficient than in the + TMS9900. As we have only 8 data bus lines we can read or write bytes + with only one cycle. The TMS9900 always has to read the memory word first + in order to write back a complete word, also when doing byte operations. +*/ +void tms9995_device::alu_mov() +{ + m_current_value = m_source_value; + if (m_instruction->byteop) + { + set_status_parity((UINT8)(m_current_value>>8)); + } + compare_and_set_lae(m_current_value, 0); + if (TRACE_STATUS) logerror("tms9995: ST = %04x (val=%04x)\n", ST, m_current_value); + // No clock pulse, as next instruction is prefetch +} + +/* + Unsigned and signed multiplication +*/ +void tms9995_device::alu_multiply() +{ + int n = 0; + UINT32 result = 0; + INT32 results = 0; + + if (m_instruction->command==MPY) + { + switch (m_instruction->state) + { + case 0: + // m_current_value <- multiplier (source) + m_source_value = m_current_value; + // m_address is the second multiplier (in a register) + m_address = ((m_instruction->IR >> 5) & 0x001e) + WP; + n = 1; + break; + case 1: + // m_current_value <- register content + result = (m_source_value & 0x0000ffff) * (m_current_value & 0x0000ffff); + m_current_value = (result >> 16) & 0xffff; + m_value_copy = result & 0xffff; + // m_address is still the register + n = 17; + break; + case 2: + m_address += 2; + m_current_value = m_value_copy; + // now write the lower 16 bit. + // If the register was R15, do not use R0 but continue writing after + // R15's address + break; + } + } + else + { + switch (m_instruction->state) + { + case 0: + // m_current_value <- multiplier (source) + m_source_value = m_current_value; + // m_address is the second multiplier (in R0) + m_address = WP; + n = 1; + break; + case 1: + // m_current_value <- register content + results = ((INT16)m_source_value) * ((INT16)m_current_value); + m_current_value = (results >> 16) & 0xffff; + m_value_copy = results & 0xffff; + // m_address is still the register + n = 16; + break; + case 2: + m_address += 2; + m_current_value = m_value_copy; + // now write the lower 16 bit. + break; + } + } + m_instruction->state++; + pulse_clock(n); +} + +void tms9995_device::alu_rtwp() +{ + switch (m_instruction->state) + { + case 0: + m_address = WP + 30; // R15 + pulse_clock(1); + break; + case 1: + ST = m_current_value; + m_address -= 2; // R14 + break; + case 2: + PC = m_current_value & 0xfffe; + m_address -= 2; // R13 + break; + case 3: + WP = m_current_value & 0xfffe; + + // Just for debugging purposes + m_servicing_interrupt = false; + + if (TRACE_OP) logerror("tms9995: RTWP restored old context (WP=%04x, PC=%04x, ST=%04x)\n", WP, PC, ST); + break; + } + m_instruction->state++; +} + +void tms9995_device::alu_sbo_sbz() +{ + INT8 displacement; + + if (m_instruction->state==0) + { + m_address = WP + 24; + } + else + { + m_cru_value = (m_instruction->command==SBO)? 1 : 0; + displacement = (INT8)(m_instruction->IR & 0xff); + m_cru_address = m_current_value + (displacement<<1); + m_count = 1; + } + m_instruction->state++; + pulse_clock(1); +} + +/* + Perform the shift operation +*/ +void tms9995_device::alu_shift() +{ + bool carry = false; + bool overflow = false; + UINT16 sign = 0; + UINT32 value; + int count; + + switch (m_instruction->state) + { + case 0: + // we have the value of the register in m_current_value + // Save it (we may have to read R0) + m_value_copy = m_current_value; + m_address_saved = m_address; + m_address = WP; + // store this in m_current_value where the R0 value will be put + m_current_value = (m_instruction->IR >> 4)& 0x000f; + if (m_current_value != 0) + { + // skip the next read operation + MPC++; + } + else + { + if (TRACE_DETAIL) logerror("tms9995: Shift operation gets count from R0\n"); + } + pulse_clock(1); + pulse_clock(1); + break; + + case 1: + count = m_current_value & 0x000f; // from the instruction or from R0 + if (count==0) count = 16; + + value = m_value_copy; + + // we are re-implementing the shift operations because we have to pulse + // the clock at each single shift anyway. + // Also, it is easier to implement the status bit setting. + // Note that count is never 0 + if (m_instruction->command == SRA) sign = value & 0x8000; + + for (int i=0; i < count; i++) + { + switch (m_instruction->command) + { + case SRL: + case SRA: + carry = ((value & 1)!=0); + value = (value >> 1) | sign; + break; + case SLA: + carry = ((value & 0x8000)!=0); + value <<= 1; + if (carry != ((value&0x8000)!=0)) overflow = true; + break; + case SRC: + carry = ((value & 1)!=0); + value = (value>>1) | (carry? 0x8000 : 0x0000); + break; + } + pulse_clock(1); + } + + m_current_value = value & 0xffff; + set_status_bit(ST_C, carry); + set_status_bit(ST_OV, overflow); + compare_and_set_lae(m_current_value, 0); + m_address = m_address_saved; // Register address + if (TRACE_STATUS) logerror("tms9995: ST = %04x (val=%04x)\n", ST, m_current_value); + break; + } + m_instruction->state++; +} + +/* + Handles ABS, DEC, DECT, INC, INCT, NEG, INV +*/ +void tms9995_device::alu_single_arithm() +{ + UINT32 dest_new = 0; + UINT32 src_val = m_current_value & 0x0000ffff; + UINT16 sign = 0; + bool check_ov = true; + + switch (m_instruction->command) + { + case ABS: + // LAECO (from original word!) + // O if >8000 + // C is always reset + set_status_bit(ST_OV, m_current_value == 0x8000); + set_status_bit(ST_C, false); + compare_and_set_lae(m_current_value, 0); + + if ((m_current_value & 0x8000)!=0) + { + dest_new = ((~src_val) & 0x0000ffff) + 1; + } + else + { + dest_new = src_val; + } + m_current_value = dest_new & 0xffff; + return; + case DEC: + // LAECO + // Carry for result value != 0xffff + // Overflow for result value == 0x7fff + dest_new = src_val + 0xffff; + sign = 0x8000; + break; + case DECT: + // Carry for result value != 0xffff / 0xfffe + // Overflow for result value = 0x7fff / 0x7ffe + dest_new = src_val + 0xfffe; + sign = 0x8000; + break; + case INC: + // LAECO + // Overflow for result value = 0x8000 + // Carry for result value = 0x0000 + dest_new = src_val + 1; + break; + case INCT: + // LAECO + // Overflow for result value = 0x8000 / 0x8001 + // Carry for result value = 0x0000 / 0x0001 + dest_new = src_val + 2; + break; + case INV: + // LAE + dest_new = ~src_val & 0xffff; + check_ov = false; + break; + case NEG: + // LAECO + // Overflow occurs for value=0x8000 + // Carry occurs for value=0 + dest_new = ((~src_val) & 0x0000ffff) + 1; + check_ov = false; + set_status_bit(ST_OV, src_val == 0x8000); + break; + case SWPB: + m_current_value = ((m_current_value << 8) | (m_current_value >> 8)) & 0xffff; + // I don't know what they are doing right now, but we lose a lot of cycles + // according to the spec (which can indeed be proved on a real system) + + // Maybe this command is used as a forced wait between accesses to the + // video system. Usually we have two byte writes to set an address in + // the VDP, with a SWPB in between. Most software for the TI-99/4A using + // the TMS9900 will run into trouble when executed on the TI-99/8 with + // the much faster TMS9995. So the SWPB may be used to as an intentional + // slowdown. + + // No status bits affected + pulse_clock(10); + return; + } + + if (check_ov) set_status_bit(ST_OV, ((src_val & 0x8000)==sign) && ((dest_new & 0x8000)!=sign)); + set_status_bit(ST_C, (dest_new & 0x10000) != 0); + m_current_value = dest_new & 0xffff; + compare_and_set_lae(m_current_value, 0); + + if (TRACE_STATUS) logerror("tms9995: ST = %04x (val=%04x)\n", ST, m_current_value); + // No clock pulse, as next instruction is prefetch +} + +/* + Store CRU. +*/ +void tms9995_device::alu_stcr() +{ + int n = 1; + switch (m_instruction->state) + { + case 0: + m_count = (m_instruction->IR >> 6) & 0x000f; + if (m_count == 0) m_count = 16; + m_instruction->byteop = (m_count < 9); + break; + case 1: + m_address_saved = m_address; + m_address = WP + 24; + break; + case 2: + m_cru_address = m_current_value; + m_cru_first_read = true; + break; + case 3: + // I don't know what is happening here, but it takes quite some time. + // May be shift operations. + m_current_value = m_cru_value; + m_address = m_address_saved; + compare_and_set_lae(m_current_value, 0); + n = 13; + if (m_instruction->byteop) + { + set_status_parity((UINT8)m_current_value); + m_current_value <<= 8; + } + else n += 8; + if (TRACE_STATUS) logerror("tms9995: ST = %04x (val=%04x)\n", ST, m_current_value); + break; + } + m_instruction->state++; + pulse_clock(n); +} + + +/* + Store status and store workspace pointer. We need to determine the + address of the register here. +*/ +void tms9995_device::alu_stst_stwp() +{ + m_address = WP + ((m_instruction->IR & 0x000f)<<1); + m_current_value = (m_instruction->command==STST)? ST : WP; +} + +/* + Test CRU bit. +*/ +void tms9995_device::alu_tb() +{ + INT8 displacement; + + switch (m_instruction->state) + { + case 0: + m_address = WP + 24; + pulse_clock(1); + break; + case 1: + displacement = (INT8)(m_instruction->IR & 0xff); + m_cru_address = m_current_value + (displacement<<1); + m_cru_first_read = true; + m_count = 1; + pulse_clock(1); + break; + case 2: + set_status_bit(ST_EQ, m_cru_value!=0); + if (TRACE_STATUS) logerror("tms9995: ST = %04x\n", ST); + break; + } + m_instruction->state++; +} + +/* + Execute. This operation is substituted after reading the word at the + given address. +*/ +void tms9995_device::alu_x() +{ + // We have the word in m_current_value. This word must now be decoded + // as if it has been acquired by the normal procedure. + decode(m_current_value); + pulse_clock(1); + + // Switch to the prefetched and decoded instruction + next_command(); +} + +/* + XOP operation. +*/ +void tms9995_device::alu_xop() +{ + switch (m_instruction->state) + { + case 0: + // we have the source address in m_address + m_address_saved = m_address; + // Format is xxxx xxnn nnxx xxxx + m_address = 0x0040 + ((m_instruction->IR & 0x03c0)>>4); + pulse_clock(1); + break; + case 1: + // m_current_value is new WP + m_value_copy = WP; // store this for later + WP = m_current_value & 0xfffe; + m_address = WP + 0x0016; // Address of new R11 + m_current_value = m_address_saved; + pulse_clock(1); + break; + case 2: + m_address = WP + 0x001e; + m_current_value = ST; + pulse_clock(1); + break; + case 3: + m_address = WP + 0x001c; + m_current_value = PC; + pulse_clock(1); + break; + case 4: + m_address = WP + 0x001a; + m_current_value = m_value_copy; + pulse_clock(1); + break; + case 5: + m_address = 0x0042 + ((m_instruction->IR & 0x03c0)>>4); + pulse_clock(1); + break; + case 6: + PC = m_current_value & 0xfffe; + set_status_bit(ST_X, true); + break; + } + m_instruction->state++; +} + +/* + Handle an interrupt. The behavior as implemented here follows + exactly the flowchart in [1] +*/ +void tms9995_device::alu_int() +{ + int pulse = 1; + + switch (m_instruction->state) + { + case 0: + PC = (PC - 2) & 0xfffe; + m_address_saved = m_address; + if (TRACE_INTD) logerror("tms9995: interrupt service (0): Prepare to read vector\n"); + break; + case 1: + pulse = 2; // two cycles (with the one at the end) + m_source_value = WP; // old WP + WP = m_current_value & 0xfffe; // new WP + m_current_value = ST; + m_address = (WP + 30)&0xfffe; + if (TRACE_INTD) logerror("tms9995: interrupt service (1): Read new WP = %04x, save ST to %04x\n", WP, m_address); + break; + case 2: + m_address = (WP + 28)&0xfffe; + m_current_value = PC; + if (TRACE_INTD) logerror("tms9995: interrupt service (2): Save PC to %04x\n", m_address); + break; + case 3: + m_address = (WP + 26)&0xfffe; + m_current_value = m_source_value; // old WP + if (TRACE_INTD) logerror("tms9995: interrupt service (3): Save WP to %04x\n", m_address); + break; + case 4: + m_address = (m_address_saved + 2) & 0xfffe; + if (TRACE_INTD) logerror("tms9995: interrupt service (4): Read PC from %04x\n", m_address); + break; + case 5: + PC = m_current_value & 0xfffe; + ST = (ST & 0xfe00) | m_intmask; + if (TRACE_INTD) logerror("tms9995: interrupt service (5): Context switch complete; WP=%04x, PC=%04x, ST=%04x\n", WP, PC, ST); + + if (((m_int_pending & PENDING_MID)!=0) && m_nmi_active) + { + if (TRACE_INTD) logerror("tms9995: interrupt service (6): NMI active after context switch\n"); + m_int_pending &= ~PENDING_MID; + m_address = 0xfffc; + m_intmask = 0; + MPC = 0; // redo the interrupt service for the NMI + } + else + { + if (m_from_reset) + { + if (TRACE_INTD) logerror("tms9995: interrupt service (6): RESET completed\n"); + // We came from the RESET interrupt + m_from_reset = false; + ST &= 0x01ff; + m_mid_flag = false; + m_mid_active = false; + // FLAG0 and FLAG1 are also set to zero after RESET ([1], sect. 2.3.1.2.2) + for (int i=0; i < 5; i++) m_flag[i] = false; + m_check_hold = true; + } + } + pulse = 0; + break; + + // If next instruction is MID opcode we will detect this in command_completed + } + m_instruction->state++; + pulse_clock(pulse); +} + +/**************************************************************************/ +UINT32 tms9995_device::execute_min_cycles() const +{ + return 2; +} + +UINT32 tms9995_device::execute_max_cycles() const +{ + return 44; +} + +UINT32 tms9995_device::execute_input_lines() const +{ + return 2; +} + +UINT32 tms9995_device::disasm_min_opcode_bytes() const +{ + return 2; +} + +UINT32 tms9995_device::disasm_max_opcode_bytes() const +{ + return 6; +} + +offs_t tms9995_device::disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options) +{ + extern CPU_DISASSEMBLE( tms9995 ); + return CPU_DISASSEMBLE_NAME(tms9995)(this, buffer, pc, oprom, opram, options); +} + + +const device_type TMS9995 = &device_creator<tms9995_device>; +const device_type TMS9995_MP9537 = &device_creator<tms9995_mp9537_device>; diff --git a/src/devices/cpu/tms9900/tms9995.h b/src/devices/cpu/tms9900/tms9995.h new file mode 100644 index 00000000000..81e2e560505 --- /dev/null +++ b/src/devices/cpu/tms9900/tms9995.h @@ -0,0 +1,446 @@ +// license:BSD-3-Clause +// copyright-holders:Michael Zapf +/* + tms9995.h + + See tms9995.c for documentation + Also see tms9900.h for types of TMS99xx processors. +*/ + +#ifndef __TMS9995_H__ +#define __TMS9995_H__ + +#include "emu.h" +#include "debugger.h" +#include "tms99com.h" + +// device type definition +extern const device_type TMS9995; +extern const device_type TMS9995_MP9537; + +enum +{ + INT_9995_RESET = 0, + INT_9995_INTREQ = 1, + INT_9995_INT1 = 2, + INT_9995_INT4 = 3 +}; + +#define MCFG_TMS9995_EXTOP_HANDLER( _extop) \ + devcb = &tms9995_device::static_set_extop_callback( *device, DEVCB_##_extop ); + +#define MCFG_TMS9995_IAQ_HANDLER( _iaq ) \ + devcb = &tms9995_device::static_set_iaq_callback( *device, DEVCB_##_iaq ); + +#define MCFG_TMS9995_CLKOUT_HANDLER( _clkout ) \ + devcb = &tms9995_device::static_set_clkout_callback( *device, DEVCB_##_clkout ); + +#define MCFG_TMS9995_HOLDA_HANDLER( _holda ) \ + devcb = &tms9995_device::static_set_holda_callback( *device, DEVCB_##_holda ); + +#define MCFG_TMS9995_DBIN_HANDLER( _dbin ) \ + devcb = &tms9995_device::static_set_dbin_callback( *device, DEVCB_##_dbin ); + +#define MCFG_TMS9995_ENABLE_OVINT( _ovint ) \ + downcast<tms9995_device*>(device)->set_overflow_interrupt( _ovint ); + + +class tms9995_device : public cpu_device +{ +public: + tms9995_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); + tms9995_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source); + + // READY input line. When asserted (high), the memory is ready for data exchange. + // We chose to use a direct method instead of a delegate to keep performance + // footprint low; this method may be called very frequently. + void set_ready(int state); + + // HOLD input line. When asserted (low), the CPU is requested to release the + // data and address bus and enter the HOLD state. The entrance of this state + // is acknowledged by the HOLDA output line. + void set_hold(int state); + + // Callbacks + template<class _Object> static devcb_base &static_set_extop_callback(device_t &device, _Object object) { return downcast<tms9995_device &>(device).m_external_operation.set_callback(object); } + template<class _Object> static devcb_base &static_set_iaq_callback(device_t &device, _Object object) { return downcast<tms9995_device &>(device).m_iaq_line.set_callback(object); } + template<class _Object> static devcb_base &static_set_clkout_callback(device_t &device, _Object object) { return downcast<tms9995_device &>(device).m_clock_out_line.set_callback(object); } + template<class _Object> static devcb_base &static_set_holda_callback(device_t &device, _Object object) { return downcast<tms9995_device &>(device).m_holda_line.set_callback(object); } + template<class _Object> static devcb_base &static_set_dbin_callback(device_t &device, _Object object) { return downcast<tms9995_device &>(device).m_dbin_line.set_callback(object); } + + // For debugger access + UINT8 debug_read_onchip_memory(offs_t addr) { return m_onchip_memory[addr & 0xff]; }; + bool is_onchip(offs_t addrb) { return (((addrb & 0xff00)==0xf000 && (addrb < 0xf0fc)) || ((addrb & 0xfffc)==0xfffc)) && !m_mp9537; } + + void set_overflow_interrupt( int enable ) { m_check_overflow = (enable!=0); } + +protected: + // device-level overrides + virtual void device_start(); + virtual void device_stop(); + virtual void device_reset(); + + // device_execute_interface overrides + virtual UINT32 execute_min_cycles() const; + virtual UINT32 execute_max_cycles() const; + virtual UINT32 execute_input_lines() const; + virtual void execute_set_input(int irqline, int state); + virtual void execute_run(); + + // device_disasm_interface overrides + virtual UINT32 disasm_min_opcode_bytes() const; + virtual UINT32 disasm_max_opcode_bytes() const; + virtual offs_t disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options); + + const address_space_config* memory_space_config(address_spacenum spacenum) const; + + UINT64 execute_clocks_to_cycles(UINT64 clocks) const { return clocks / 4.0; } + UINT64 execute_cycles_to_clocks(UINT64 cycles) const { return cycles * 4.0; } + + // Variant of the TMS9995 without internal RAM and decrementer + bool m_mp9537; + +private: + // State / debug management + UINT16 m_state_any; + static const char* s_statename[]; + void state_import(const device_state_entry &entry); + void state_export(const device_state_entry &entry); + void state_string_export(const device_state_entry &entry, std::string &str); + UINT16 read_workspace_register_debug(int reg); + void write_workspace_register_debug(int reg, UINT16 data); + + // TMS9995 hardware registers + UINT16 WP; // Workspace pointer + UINT16 PC; // Program counter + UINT16 ST; // Status register + + // The TMS9995 has a prefetch feature which causes a wrong display of the PC. + // We use this additional member for the debugger only. + UINT16 PC_debug; + + // 256 bytes of onchip memory + UINT8 m_onchip_memory[256]; + + const address_space_config m_program_config; + const address_space_config m_io_config; + address_space* m_prgspace; + address_space* m_cru; + + + // Processor states + bool m_idle_state; + bool m_nmi_state; + bool m_irq_state; + bool m_hold_state; + + // READY handling. The READY line is operated before the clock + // pulse falls. As the ready line is only set once in this emulation we + // keep the level in a buffer (like a latch) + bool m_ready_bufd; // buffered state + bool m_ready; // sampled value + + // Auto-wait state generation + bool m_request_auto_wait_state; + bool m_auto_wait; + + // Cycle counter + int m_icount; + + // Phase of the memory access + int m_mem_phase; + + // Check the READY line? + bool m_check_ready; + + // Check the HOLD line + bool m_check_hold; + + // For multi-pass operations. For instance, memory word accesses are + // executed as two consecutive byte accesses. CRU accesses are repeated + // single-bit accesses. + int m_pass; + + // For parity operations + int m_parity; + + // For Format 1 instruction; determines whether the next operand address + // derivation is for the source or address operand + bool m_get_destination; + + // Used for situations when a command is byte-oriented, but the memory access + // must be word-oriented. Example: MOVB *R1,R0; we must read the full word + // from R1 to get the address. + bool m_word_access; + + // Interrupt handling + bool m_nmi_active; + bool m_int1_active; + bool m_int4_active; + bool m_int_decrementer; + bool m_int_overflow; + + bool m_reset; + bool m_from_reset; + bool m_mid_flag; + bool m_mid_active; + + int m_decrementer_clkdiv; + bool m_servicing_interrupt; + + // Flag field + int m_int_pending; + + // The TMS9995 is capable of raising an internal interrupt on + // arithmetic overflow, depending on the status register Overflow Enable bit. + // However, the specs also say that this feature is non-functional in the + // currently available chip. Thus we have an option to turn it off so that + // software will not change its behavior on overflows. + bool m_check_overflow; + + // Service pending interrupts + void service_interrupt(); + + // Issue clock pulses. The TMS9995 uses one (output) clock cycle per machine cycle. + inline void pulse_clock(int count); + + // Signal the hold state via the external line + inline void set_hold_state(bool state); + + // Only used for the DIV(S) operations. It seems sufficient to let the + // command terminate at this point, so this method just calls command_terminated. + void abort_operation(void); + + // Decode the given 16-bit value which has been retrieved by a prefetch or + // during an X operation. + void decode(UINT16 inst); + + // Store the interrupt mask part of the ST. This is used when processing + // an interrupt, passing the new mask from the service_interrupt part to + // the program part. + int m_intmask; + + // Stored address + UINT16 m_address; + + // Stores the recently read word or the word to be written + UINT16 m_current_value; + + // Stores the value of the source operand in multi-operand instructions + UINT16 m_source_value; + + // During indexed addressing, this value is added to get the final address value. + UINT16 m_address_add; + + // During indirect/auto-increment addressing, this copy of the address must + // be preserved while writing the new value to the register. + UINT16 m_address_saved; + + // Another copy of the address + UINT16 m_address_copy; + + // Copy of the value + UINT16 m_value_copy; + + // Stores the recent register number. Only used to pass the register + // number during the operand address derivation. + int m_regnumber; + + // Stores the number of bits or shift operations + int m_count; + + // ============== Decrementer ======================= + void trigger_decrementer(); + + // Start value + UINT16 m_starting_count_storage_register; + + // Current decrementer value. + UINT16 m_decrementer_value; + + // ============== CRU support ====================== + + UINT16 m_cru_address; + UINT16 m_cru_value; + bool m_cru_first_read; + int m_cru_bits_left; + UINT32 m_cru_read; + + // CPU-internal CRU flags + bool m_flag[16]; + + // ============== Prefetch support ===================== + + struct decoded_instruction + { + UINT16 IR; + UINT16 command; + const UINT8* program; + bool byteop; + int state; + }; + + int m_instindex; + + // We implement the prefetch mechanism by two separate datasets for + // the decoded commands. When the previous command has completed, the + // pointer is just switched to the other one. + tms9995_device::decoded_instruction m_decoded[2]; + tms9995_device::decoded_instruction* m_instruction; + + // ================ Microprogram support ======================== + + // Set up lookup table + void build_command_lookup_table(); + + // Sequence of micro-operations + typedef const UINT8* microprogram; + + // Method pointer + typedef void (tms9995_device::*ophandler)(void); + + // Opcode list entry + struct tms_instruction + { + UINT16 opcode; + int id; + int format; + microprogram prog; // Microprogram + }; + + // Lookup table entry + struct lookup_entry + { + lookup_entry *next_digit; + const tms_instruction *entry; + }; + + // Pointer to the lookup table; the entry point for searching the command + lookup_entry* m_command_lookup_table; + + // List of allocated tables (used for easy clean-up on exit) + lookup_entry* m_lotables[32]; + + // List of pointers for micro-operations + static const tms9995_device::ophandler s_microoperation[]; + + static const tms9995_device::tms_instruction s_command[]; + + // Micro-operation program counter (as opposed to the program counter PC) + int MPC; + + // Calling microprogram (used when data derivation is called) + const UINT8* m_caller; + int m_caller_MPC; + + // Table of microprograms + static const microprogram mp_table[]; + + // Used to display the number of consumed cycles in the log. + int m_first_cycle; + + // Status register update + inline void set_status_bit(int bit, bool state); + inline void compare_and_set_lae(UINT16 value1, UINT16 value2); + void set_status_parity(UINT8 value); + + // Micro-operation declarations + void int_prefetch_and_decode(); + void prefetch_and_decode(); + void mem_read(); + void mem_write(); + inline void word_read(); + inline void word_write(); + void operand_address_subprogram(); + void increment_register(); + void indexed_addressing(); + void set_immediate(); + void return_with_address(); + void return_with_address_copy(); + void cru_input_operation(); + void cru_output_operation(); + void command_completed(); + void next_command(); + + // ALU operations for specific commands + void alu_nop(); + void alu_add_s_sxc(); + void alu_b(); + void alu_blwp(); + void alu_c(); + void alu_ci(); + void alu_clr_seto(); + void alu_divide(); + void alu_divide_signed(); + void alu_external(); + void alu_f3(); + void alu_imm_arithm(); + void alu_jump(); + void alu_ldcr(); + void alu_li(); + void alu_limi_lwpi(); + void alu_lst_lwp(); + void alu_mov(); + void alu_multiply(); + void alu_multiply_signed(); + void alu_rtwp(); + void alu_sbo_sbz(); + void alu_shift(); + void alu_single_arithm(); + void alu_stcr(); + void alu_stst_stwp(); + void alu_tb(); + void alu_x(); + void alu_xop(); + void alu_int(); + + // ================ Connections ==================== + + // Trigger external operation. This is achieved by putting a special value in + // the most significant three bits of the data bus and pulsing the CRUCLK line. + // Accordingly, we have + // + // D0 D1 D2 + // 0 0 0 normal CRU access + // 0 1 0 IDLE + // 0 1 1 RSET + // 1 0 1 CKON + // 1 1 0 CKOF + // 1 1 1 LREX + // + // We could realize this via the CRU access as well, but the data bus access + // is not that simple to emulate. For the sake of homogenity between the + // chip emulations we use a dedicated callback. + devcb_write8 m_external_operation; + + // Signal to the outside world that we are now getting an instruction (IAQ). + // In the real hardware this line is shared with the HOLDA line, and the + // /MEMEN line is used to decide which signal we have on the line. We do not + // emulate the /MEMEN line, so we have to use two separate lines. + devcb_write_line m_iaq_line; + + // Clock output. + devcb_write_line m_clock_out_line; + + // Asserted when the CPU is in a HOLD state + devcb_write_line m_holda_line; + + // DBIN line. When asserted (high), the CPU has disabled the data bus output buffers. + devcb_write_line m_dbin_line; +}; + + +/* + Variant of the TMS9995 without on-chip RAM; used in the TI-99/8 console +*/ +class tms9995_mp9537_device : public tms9995_device +{ +public: + tms9995_mp9537_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : tms9995_device(mconfig, TMS9995_MP9537, "TMS9995-MP9537", tag, owner, clock, "tms9995_mp9537", __FILE__) + { + m_mp9537 = true; + } +}; + +#endif /* __TMS9995_H__ */ diff --git a/src/devices/cpu/tms9900/tms99com.h b/src/devices/cpu/tms9900/tms99com.h new file mode 100644 index 00000000000..3a1aeeb57ee --- /dev/null +++ b/src/devices/cpu/tms9900/tms99com.h @@ -0,0 +1,93 @@ +// license:BSD-3-Clause +// copyright-holders:Michael Zapf +/* + Common definitions for all TMS processors + + Types of TMS99xx processors: + TI990/9 Early implementation, used in a few real-world applications, 1974 + very similar to mapper-less 990/10 and tms9900, but the Load + process is different + + TI990/10 Original multi-chip implementation for minicomputer systems, 1975 + + TI990/12 Multi-chip implementation, faster than 990/10. Huge instruction set + + TMS9900 Mono-chip implementation, 1976. Used in the TI-99/4(A) computer. + + TMS9940 Microcontroller with 2kb ROM, 128b RAM, decrementer, CRU bus, 1979 + + TMS9980 8-bit variant of tms9900. Two distinct chips actually : tms9980a + and tms9981 with an extra clock and simplified power supply + + TMS9985 9940 with 8kb ROM, 256b RAM, and a 8-bit external bus, c. 1978 (never released) + + TMS9989 Improved 9980, used in military hardware. + + SBP68689 Improved 9989, built as an ASIC as 9989 was running scarce + + TMS9995 TMS9985-like, with many improvements (but no ROM). Used in the + TI-99/8 prototype and the Geneve computer. + + TMS99000 Improved mono-chip implementation, meant to replace 990/10, 1981 + TMS99105 This chip is available in several variants which are similar + TMS99110 but emulate additional instructions, thanks to the so-called + macrostore feature. + + In this implementation we only consider TMS9900, 9980, and 9995. The + remaining types are implemented on an own code base as they introduce + significant changes (e.g. privileged mode, address mapper). +*/ + +#ifndef __TMS99COMMON_H__ +#define __TMS99COMMON_H__ + +#define MCFG_TMS99xx_ADD(_tag, _device, _clock, _prgmap, _iomap ) \ + MCFG_DEVICE_ADD(_tag, _device, _clock) \ + MCFG_DEVICE_PROGRAM_MAP(_prgmap) \ + MCFG_DEVICE_IO_MAP(_iomap) + +#define MCFG_TMS99xx_EXTOP_HANDLER( _extop) \ + devcb = &tms99xx_device::static_set_extop_callback( *device, DEVCB_##_extop ); + +#define MCFG_TMS99xx_INTLEVEL_HANDLER( _intlevel ) \ + devcb = &tms99xx_device::static_set_intlevel_callback( *device, DEVCB_##_intlevel ); + +#define MCFG_TMS99xx_IAQ_HANDLER( _iaq ) \ + devcb = &tms99xx_device::static_set_iaq_callback( *device, DEVCB_##_iaq ); + +#define MCFG_TMS99xx_CLKOUT_HANDLER( _clkout ) \ + devcb = &tms99xx_device::static_set_clkout_callback( *device, DEVCB_##_clkout ); + +#define MCFG_TMS99xx_WAIT_HANDLER( _wait ) \ + devcb = &tms99xx_device::static_set_wait_callback( *device, DEVCB_##_wait ); + +#define MCFG_TMS99xx_HOLDA_HANDLER( _holda ) \ + devcb = &tms99xx_device::static_set_holda_callback( *device, DEVCB_##_holda ); + +#define MCFG_TMS99xx_DBIN_HANDLER( _dbin ) \ + devcb = &tms99xx_device::static_set_dbin_callback( *device, DEVCB_##_dbin ); + +enum +{ + TI990_10_ID = 1, + TMS9900_ID = 3, + TMS9940_ID = 4, + TMS9980_ID = 5, + TMS9985_ID = 6, + TMS9989_ID = 7, + TMS9995_ID = 9, + TMS99000_ID = 10, + TMS99105A_ID = 11, + TMS99110A_ID = 12 +}; + +enum +{ + IDLE_OP = 2, + RSET_OP = 3, + CKOF_OP = 5, + CKON_OP = 6, + LREX_OP = 7 +}; + +#endif /* __TMS99COMMON_H__ */ |