mirror of
https://github.com/holub/mame
synced 2025-05-28 08:33:05 +03:00
3346 lines
86 KiB
C
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>;
|