mame/src/emu/cpu/tms9900/tms9995.c
2013-10-21 23:25:36 +00:00

3346 lines
86 KiB
C

// 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
V_CC |10 31| V_SS
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
V_CC +5V supply
V_SS 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:
- Fine-tune cycles
- 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
};
#define LOG logerror
#define VERBOSE 1
/****************************************************************************
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)
{
}
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()
{
const tms9995_config *conf = reinterpret_cast<const tms9995_config *>(static_config());
assert (conf != NULL);
// 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(conf->external_callback, *this);
m_iaq_line.resolve(conf->iaq_line, *this);
m_clock_out_line.resolve(conf->clock_out, *this);
m_holda_line.resolve(conf->holda_line, *this);
m_dbin_line.resolve(conf->dbin_line, *this);
m_mp9537 = (conf->mode==NO_INTERNAL_RAM);
m_check_overflow = (conf->overflow==OVERFLOW_INT);
if (VERBOSE>0) LOG("tms9995: Configured with%s internal memory and%s overflow interrupt\n", m_mp9537? " no" : "", m_check_overflow? "" : " no");
// 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;
// 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();
}
void tms9995_device::device_stop()
{
int k = 0;
if (VERBOSE>8) LOG("tms9995: Deleting lookup tables\n");
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
}
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, astring &string)
{
static const char *statestr = "LAECOPX-----IIII";
char flags[16];
memset(flags, 0x00, 16);
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;
}
}
string.cpy(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;
bool onchip = (((addrb & 0xff00)==0xf000 && (addrb < 0xf0fc)) || ((addrb & 0xfffc)==0xfffc)) && !m_mp9537;
if (onchip)
{
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;
bool onchip = (((addrb & 0xff00)==0xf000 && (addrb < 0xf0fc)) || ((addrb & 0xfffc)==0xfffc)) && !m_mp9537;
if (onchip)
{
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[] =
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
PREFETCH, // 1 (1)
MEMORY_WRITE, // 1 (1)
END
};
MICROPROGRAM(b_mp)
{
OPERAND_ADDR,
ALU_NOP, // Don't read, just use the address
ALU_B,
PREFETCH,
ALU_NOP, // Don't save the return address
END
};
MICROPROGRAM(bl_mp)
{
OPERAND_ADDR,
ALU_NOP, // Don't read, just use the address
ALU_B, // Re-use the alu operation from B
PREFETCH,
ALU_NOP,
MEMORY_WRITE, // Write R11
ALU_NOP,
END
};
MICROPROGRAM(blwp_mp)
{
OPERAND_ADDR, // Determine source address
MEMORY_READ,
ALU_BLWP, // Got new WP, save it; increase address, save
MEMORY_WRITE, // save old ST to new R15
ALU_BLWP,
MEMORY_WRITE, // save old PC to new R14
ALU_BLWP,
MEMORY_WRITE, // save old WP to new R13
ALU_BLWP, // retrieve address
MEMORY_READ, // Read new PC
ALU_BLWP, // Set new PC
PREFETCH,
ALU_NOP,
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
END
};
MICROPROGRAM(ci_mp)
{
MEMORY_READ, // 1 (reg)
SET_IMM, // 0
MEMORY_READ, // 1 (imm)
ALU_CI, // (1) set status
PREFETCH, // 1
ALU_NOP, // 1
END
};
MICROPROGRAM(coc_czc_mp)
{
OPERAND_ADDR,
MEMORY_READ,
ALU_F3,
MEMORY_READ,
ALU_F3,
PREFETCH,
ALU_NOP,
END
};
MICROPROGRAM(clr_seto_mp)
{
OPERAND_ADDR,
ALU_NOP,
ALU_CLR_SETO, // (1)
PREFETCH, // 1
MEMORY_WRITE, // 1
END
};
MICROPROGRAM(divide_mp)
{
OPERAND_ADDR, // Address of divisor S in Q=W1W2/S
MEMORY_READ, // Get S
ALU_DIV,
MEMORY_READ, // Get W1
ALU_DIV, // Check for overflow; skip next instruction if not
ABORT,
MEMORY_READ, // Get W2
ALU_DIV, // Calculate quotient
MEMORY_WRITE, // Write quotient to &W1
ALU_DIV,
PREFETCH,
MEMORY_WRITE, // Write remainder to &W2
END
};
MICROPROGRAM(divide_signed_mp)
{
OPERAND_ADDR, // Address of divisor S in Q=W1W2/S
MEMORY_READ, // Get S
ALU_DIVS,
MEMORY_READ, // Get W1
ALU_DIVS, //
MEMORY_READ, // Get W2
ALU_DIVS, // Check for overflow, skip next instruction if not
ABORT,
ALU_DIVS, // Calculate quotient
MEMORY_WRITE, // Write quotient to &W1
ALU_DIVS,
PREFETCH,
MEMORY_WRITE, // Write remainder to &W2
END
};
MICROPROGRAM(external_mp)
{
ALU_NOP,
ALU_NOP,
ALU_NOP,
ALU_NOP,
ALU_NOP,
ALU_EXTERNAL,
PREFETCH,
ALU_NOP,
END
};
MICROPROGRAM(imm_arithm_mp)
{
MEMORY_READ,
SET_IMM, // 0
MEMORY_READ, // 1 (1)
ALU_IMM_ARITHM, // 0
PREFETCH, // 1 (1)
MEMORY_WRITE,
END
};
MICROPROGRAM(jump_mp)
{
ALU_JUMP,
PREFETCH,
ALU_NOP,
END
};
MICROPROGRAM(ldcr_mp)
{
ALU_LDCR,
OPERAND_ADDR,
MEMORY_READ, // Get source data
ALU_LDCR, // Save it, point to R12
WORD_READ, // Get R12
ALU_LDCR, // Prepare CRU operation
CRU_OUTPUT,
ALU_NOP,
PREFETCH,
ALU_NOP,
END
};
MICROPROGRAM(li_mp)
{
SET_IMM, // 0
MEMORY_READ, // 1 (1)
ALU_LI, // 0
PREFETCH, // 1 (1)
MEMORY_WRITE,
END
};
MICROPROGRAM(limi_lwpi_mp)
{
SET_IMM, // 0
MEMORY_READ, // 1 (1)
ALU_NOP, // 1
ALU_LIMIWP, // (1)
PREFETCH, // 1
ALU_NOP, // 1
END
};
MICROPROGRAM(lst_lwp_mp)
{
MEMORY_READ,
ALU_NOP,
ALU_LSTWP,
PREFETCH,
ALU_NOP,
END
};
MICROPROGRAM(mov_mp)
{
OPERAND_ADDR, // 0
MEMORY_READ, // 1 (1)
OPERAND_ADDR, // 0
ALU_MOV, // 1
PREFETCH,
MEMORY_WRITE, // 1 (1)
END
};
MICROPROGRAM(multiply_mp)
{
OPERAND_ADDR,
MEMORY_READ,
ALU_MPY,
MEMORY_READ,
ALU_MPY,
MEMORY_WRITE,
ALU_MPY,
PREFETCH,
MEMORY_WRITE,
END
};
MICROPROGRAM(rtwp_mp)
{
ALU_RTWP,
MEMORY_READ,
ALU_RTWP,
MEMORY_READ,
ALU_RTWP,
MEMORY_READ,
ALU_RTWP,
PREFETCH,
ALU_NOP,
END
};
MICROPROGRAM(sbo_sbz_mp)
{
ALU_SBO_SBZ, // Set address = &R12
WORD_READ, // Read R12
ALU_SBO_SBZ, // Add offset
CRU_OUTPUT, // output via CRU
ALU_NOP,
PREFETCH,
ALU_NOP,
END
};
MICROPROGRAM(shift_mp)
{
MEMORY_READ,
ALU_SHIFT, // skip next operation if count != 0
MEMORY_READ, // if count=0 we must read R0
ALU_SHIFT, // do the shift
PREFETCH,
MEMORY_WRITE,
END
};
MICROPROGRAM(single_arithm_mp)
{
OPERAND_ADDR,
MEMORY_READ, // This one is not done for CLR/SETO
ALU_SINGLE_ARITHM,
PREFETCH,
MEMORY_WRITE,
END
};
MICROPROGRAM(stcr_mp)
{
ALU_STCR, // Check for byte operation
OPERAND_ADDR, // Source operand
ALU_STCR, // Save, set R12
WORD_READ, // Read R12
ALU_STCR,
CRU_INPUT,
ALU_STCR,
PREFETCH,
MEMORY_WRITE,
END
};
MICROPROGRAM(stst_stwp_mp)
{
ALU_STSTWP,
ALU_NOP,
PREFETCH,
MEMORY_WRITE,
END
};
MICROPROGRAM(tb_mp)
{
ALU_TB,
WORD_READ,
ALU_TB,
CRU_INPUT,
ALU_TB,
PREFETCH,
ALU_NOP,
END
};
MICROPROGRAM(x_mp)
{
OPERAND_ADDR,
MEMORY_READ,
ALU_X,
END // should not be reached
};
MICROPROGRAM(xop_mp)
{
OPERAND_ADDR, // Determine source address
ALU_XOP, // Save it; determine XOP number
MEMORY_READ, // Read new WP
ALU_XOP, //
MEMORY_WRITE, // save source address to new R11
ALU_XOP,
MEMORY_WRITE, // save old ST to new R15
ALU_XOP,
MEMORY_WRITE, // save old PC to new R14
ALU_XOP,
MEMORY_WRITE, // save old WP to new R13
ALU_XOP,
MEMORY_READ, // Read new PC
ALU_XOP, // set new PC, set X flag
PREFETCH,
ALU_NOP,
END
};
MICROPROGRAM(xor_mp)
{
OPERAND_ADDR,
MEMORY_READ,
ALU_F3,
MEMORY_READ,
ALU_F3,
PREFETCH,
MEMORY_WRITE,
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 (VERBOSE>8) LOG("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 (VERBOSE>8) LOG("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 (VERBOSE>8) LOG("tms9995: found a table at bitcount=%d\n", bitcount);
}
table = table[cmdindex].next_digit;
bitcount = bitcount+4;
opcode <<= 4;
cmdindex = (opcode>>12) & 0x000f;
if (VERBOSE>8) LOG("tms9995: next index=%x\n", cmdindex);
}
if (VERBOSE>8) LOG("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 (VERBOSE>8) LOG("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 (VERBOSE>8) LOG("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 (VERBOSE>5) LOG("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 (VERBOSE>2) LOG("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 (VERBOSE>6) LOG("tms9995: hold state\n");
pulse_clock(1);
}
else
{
set_hold_state(false);
m_check_ready = false;
if (VERBOSE>8) LOG("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 (VERBOSE>5) LOG("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 (VERBOSE>3) LOG("tms9995: NMI interrupt line state=%d\n", state);
}
else
{
if (irqline == INT_9995_INT1)
{
m_int1_active = m_flag[2] = (state==ASSERT_LINE);
if (VERBOSE>3) LOG("tms9995: Line INT1 state=%d\n", state);
}
else
{
if (irqline == INT_9995_INT4)
{
if (VERBOSE>3) LOG("tms9995: Line INT4/EC state=%d\n", state);
if (m_flag[0]==false)
{
if (VERBOSE>7) LOG("tms9995: set as interrupt\n");
m_int4_active = m_flag[4] = (state==ASSERT_LINE);
}
else
{
if (VERBOSE>7) LOG("tms9995: set as event count\n");
trigger_decrementer();
}
}
else
{
if (VERBOSE>0) LOG("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++)
{
m_clock_out_line(ASSERT_LINE);
m_ready = m_ready_bufd && !m_request_auto_wait_state; // get the latched READY state
m_clock_out_line(CLEAR_LINE);
m_icount--; // This is the only location where we count down the cycles.
if (VERBOSE>7)
{
if (m_check_ready) LOG("tms9995: pulse_clock, READY=%d, auto_wait=%d\n", m_ready_bufd? 1:0, m_auto_wait? 1:0);
else LOG("tms9995: pulse_clock\n");
}
m_request_auto_wait_state = false;
if (m_flag[0] == false && m_flag[1] == true) trigger_decrementer();
}
}
/*
Enter the hold state.
*/
void tms9995_device::set_hold(int state)
{
m_hold_state = (state==ASSERT_LINE);
if (VERBOSE>7) LOG("tms9995: set HOLD = %d\n", state);
if (!m_hold_state)
{
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 (VERBOSE>7) LOG("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) 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 (VERBOSE>8) LOG("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
if (VERBOSE>0) LOG("tms9995: Unknown opcode %04x, will trigger MID\n", inst);
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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>3) LOG("tms9995: Interrupt occured, terminate IDLE state\n");
}
PC = PC + 2; // PC must be advanced (see flow chart), but no prefetch
if (VERBOSE>7) LOG("tms9995: Interrupts pending; no prefetch; advance PC to %04x\n", PC);
return;
}
else
{
if (VERBOSE>7) LOG("tms9995: Checking interrupts ... none pending\n");
// No pending interrupts
if (m_idle_state)
{
if (VERBOSE>7) LOG("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;
m_iaq_line(ASSERT_LINE);
m_address = PC;
if (VERBOSE>5) LOG("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
m_iaq_line(CLEAR_LINE);
if (VERBOSE>5) LOG("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 (VERBOSE>3) LOG("tms9995: ===== Next operation %04x (%s) at %04x =====\n", m_instruction->IR, opname[m_instruction->command], 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 (VERBOSE>4)
{
LOG("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) LOG(", consumed %d cycles", cycles);
LOG(" +++++\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_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 (VERBOSE>0) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("tms9995: ***** INT1 pending\n");
}
else
{
if ((m_int_pending & PENDING_OVERFLOW)!=0)
{
vectorpos = 0x0008;
m_int_pending &= ~PENDING_OVERFLOW;
m_intmask = 0x0001;
if (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("tms9995: ***** DECR pending\n");
}
else
{
vectorpos = 0x0010;
m_intmask = 0x0003;
m_int_pending &= ~PENDING_LEVEL4;
m_flag[4] = false;
if (VERBOSE>7) LOG("tms9995: ***** INT4 pending\n");
}
}
}
}
}
}
if (VERBOSE>6) LOG("tms9995: ********* triggered an interrupt with vector %04x/%04x\n", vectorpos, vectorpos+2);
// 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 (VERBOSE>5) LOG("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;
}
bool onchip = (((m_address & 0xff00)==0xf000 && (m_address < 0xf0fc)) || ((m_address & 0xfffc)==0xfffc)) && !m_mp9537;
if (onchip)
{
if (VERBOSE>5) LOG("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);
}
}
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
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 (VERBOSE>7) LOG("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 (VERBOSE>3) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>3) LOG("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 (VERBOSE>5) LOG("tms9995: setting decrementer\n");
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 (VERBOSE>2) LOG("tms9995: Setting decrementer to %04x, PC=%04x\n", m_current_value, PC);
pulse_clock(1);
return;
}
bool onchip = (((m_address & 0xff00)==0xf000 && (m_address < 0xf0fc)) || ((m_address & 0xfffc)==0xfffc)) && !m_mp9537;
if (onchip)
{
if (VERBOSE>3) LOG("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;
}
}
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
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 (VERBOSE>7) LOG("tms9995: set address bus %04x\n", address);
m_prgspace->set_address(address);
if (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("tms9995: set address bus %04x\n", m_address | 1);
m_prgspace->set_address(m_address | 1);
if (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>5) LOG("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 (VERBOSE>2) LOG("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 (VERBOSE>8) LOG("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 (VERBOSE>8) LOG("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 (VERBOSE>5) LOG("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 (VERBOSE>4) LOG("tms9995: decrementer reached 0\n");
m_decrementer_value = m_starting_count_storage_register;
if (m_flag[1]==true)
{
if (VERBOSE>4) LOG("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 (VERBOSE>8) LOG("tms9995: indexed addressing\n");
MPC = 16; // indexed
}
else
{
if (VERBOSE>8) LOG("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 (VERBOSE>8) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>5) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("tms9995: ST = %04x (val1=%04x, val2=%04x)\n", ST, m_source_value, m_current_value);
}
void tms9995_device::alu_clr_seto()
{
if (VERBOSE>7) LOG("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 (VERBOSE>4) LOG("tms9995: Entering IDLE state\n");
m_idle_state = true;
}
if (m_instruction->command == RSET)
{
ST &= 0xfff0;
if (VERBOSE>3) LOG("tms9995: New ST = %04x\n", ST);
}
m_external_operation((m_instruction->IR >> 5) & 0x07, 1);
}
/*
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 (VERBOSE>7) LOG("tms9995: ST = %04x\n", ST);
break;
}
pulse_clock(1);
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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("tms9995: Jump condition false\n");
}
else
{
if (VERBOSE>7) LOG("tms9995: Jump condition true\n");
PC = (PC + (displacement<<1)) & 0xfffe;
}
pulse_clock(1);
}
/*
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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>4) LOG("tms9995: LIMI sets ST = %04x\n", ST);
pulse_clock(1); // needs one more than LWPI
}
else
{
WP = m_current_value & 0xfffe;
if (VERBOSE>4) LOG("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 (VERBOSE>7) LOG("tms9995: new ST = %04x\n", ST);
pulse_clock(1);
}
else
{
WP = m_current_value & 0xfffe;
if (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("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 = 16;
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()
{
int n = 0;
switch (m_instruction->state)
{
case 0:
m_address = WP + 30; // R15
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;
n = 1;
if (VERBOSE>4) LOG("tms9995: RTWP restored old context (WP=%04x, PC=%04x, ST=%04x)\n", WP, PC, ST);
break;
}
m_instruction->state++;
pulse_clock(n);
}
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++;
pulse_clock(1);
}
else
{
if (VERBOSE>8) LOG("tms9995: Shift operation gets count from R0\n");
}
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 (VERBOSE>7) LOG("tms9995: ST = %04x (val=%04x)\n", ST, m_current_value);
break;
}
m_instruction->state++;
pulse_clock(1);
}
/*
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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("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()
{
int n = 1;
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);
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;
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;
break;
case 5:
m_address = 0x0042 + ((m_instruction->IR & 0x03c0)>>4);
break;
case 6:
PC = m_current_value & 0xfffe;
set_status_bit(ST_X, true);
n = 0;
break;
}
m_instruction->state++;
pulse_clock(n);
}
/*
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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("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 (VERBOSE>7) LOG("tms9995: interrupt service (3): Save WP to %04x\n", m_address);
break;
case 4:
m_address = (m_address_saved + 2) & 0xfffe;
if (VERBOSE>7) LOG("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 (VERBOSE>5) LOG("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 (VERBOSE>5) LOG("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 (VERBOSE>5) LOG("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>;