summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu
diff options
context:
space:
mode:
author Michael Zapf <michael.zapf@mizapf.de>2012-06-07 20:14:04 +0000
committer Michael Zapf <michael.zapf@mizapf.de>2012-06-07 20:14:04 +0000
commit93a03892728abab8479dd0bc872537d540ff375d (patch)
treedcd25508f7e14de0e3ec1447deccbea8451c8bc0 /src/emu
parent531a86f2726e8f5173fe8881fd754d2a2d0d243c (diff)
New TMS9900 and TMS9995 implementation with proper control line handling
and wait state generation. Previous implementation has been renamed to *l.{c,h} and drivers point to the old implementation until they are adapted to the new version. [Michael Zapf]
Diffstat (limited to 'src/emu')
-rw-r--r--src/emu/cpu/cpu.mak24
-rw-r--r--src/emu/cpu/tms9900/99xxcore.h46
-rw-r--r--src/emu/cpu/tms9900/ti990_10.c13
-rw-r--r--src/emu/cpu/tms9900/ti990_10l.c13
-rw-r--r--src/emu/cpu/tms9900/tms9900.c2519
-rw-r--r--src/emu/cpu/tms9900/tms9900.h531
-rw-r--r--src/emu/cpu/tms9900/tms9900l.c15
-rw-r--r--src/emu/cpu/tms9900/tms9900l.h210
-rw-r--r--src/emu/cpu/tms9900/tms9980a.c13
-rw-r--r--src/emu/cpu/tms9900/tms9980al.c13
-rw-r--r--src/emu/cpu/tms9900/tms9995.c3290
-rw-r--r--src/emu/cpu/tms9900/tms9995.h433
-rw-r--r--src/emu/cpu/tms9900/tms9995l.c13
13 files changed, 6891 insertions, 242 deletions
diff --git a/src/emu/cpu/cpu.mak b/src/emu/cpu/cpu.mak
index 8346c7998bc..cdc2d9cde2e 100644
--- a/src/emu/cpu/cpu.mak
+++ b/src/emu/cpu/cpu.mak
@@ -1607,29 +1607,37 @@ $(CPUOBJ)/tms7000/7000dasm.o: $(CPUSRC)/tms7000/tms7000.h \
ifneq ($(filter TMS9900,$(CPUS)),)
OBJDIRS += $(CPUOBJ)/tms9900
CPUOBJS += $(CPUOBJ)/tms9900/tms9900.o
-CPUOBJS += $(CPUOBJ)/tms9900/tms9980a.o
+CPUOBJS += $(CPUOBJ)/tms9900/tms9900l.o
+CPUOBJS += $(CPUOBJ)/tms9900/tms9980al.o
CPUOBJS += $(CPUOBJ)/tms9900/tms9995.o
-CPUOBJS += $(CPUOBJ)/tms9900/ti990_10.o
+CPUOBJS += $(CPUOBJ)/tms9900/tms9995l.o
+CPUOBJS += $(CPUOBJ)/tms9900/ti990_10l.o
DASMOBJS += $(CPUOBJ)/tms9900/9900dasm.o
endif
$(CPUOBJ)/tms9900/tms9900.o: $(CPUSRC)/tms9900/tms9900.c \
- $(CPUSRC)/tms9900/tms9900.h \
+ $(CPUSRC)/tms9900/tms9900.h
+
+$(CPUOBJ)/tms9900/tms9900l.o: $(CPUSRC)/tms9900/tms9900l.c \
+ $(CPUSRC)/tms9900/tms9900l.h \
$(CPUSRC)/tms9900/99xxcore.h \
$(CPUSRC)/tms9900/99xxstat.h
-$(CPUOBJ)/tms9900/tms9980a.o: $(CPUSRC)/tms9900/tms9980a.c \
- $(CPUSRC)/tms9900/tms9900.h \
+$(CPUOBJ)/tms9900/tms9980al.o: $(CPUSRC)/tms9900/tms9980al.c \
+ $(CPUSRC)/tms9900/tms9900l.h \
$(CPUSRC)/tms9900/99xxcore.h \
$(CPUSRC)/tms9900/99xxstat.h
$(CPUOBJ)/tms9900/tms9995.o: $(CPUSRC)/tms9900/tms9995.c \
- $(CPUSRC)/tms9900/tms9900.h \
+ $(CPUSRC)/tms9900/tms9900.h
+
+$(CPUOBJ)/tms9900/tms9995l.o: $(CPUSRC)/tms9900/tms9995l.c \
+ $(CPUSRC)/tms9900/tms9900l.h \
$(CPUSRC)/tms9900/99xxcore.h \
$(CPUSRC)/tms9900/99xxstat.h
-$(CPUOBJ)/tms9900/ti990_10.o: $(CPUSRC)/tms9900/ti990_10.c \
- $(CPUSRC)/tms9900/tms9900.h \
+$(CPUOBJ)/tms9900/ti990_10l.o: $(CPUSRC)/tms9900/ti990_10l.c \
+ $(CPUSRC)/tms9900/tms9900l.h \
$(CPUSRC)/tms9900/99xxcore.h \
$(CPUSRC)/tms9900/99xxstat.h
diff --git a/src/emu/cpu/tms9900/99xxcore.h b/src/emu/cpu/tms9900/99xxcore.h
index 29f64079cf8..32e4242f570 100644
--- a/src/emu/cpu/tms9900/99xxcore.h
+++ b/src/emu/cpu/tms9900/99xxcore.h
@@ -85,78 +85,78 @@ Other references can be found on spies.com:
*/
-#include "tms9900.h"
+#include "tms9900l.h"
#if (TMS99XX_MODEL == TI990_10_ID)
#define TMS99XX_PREFIX ti990_10
- #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( ti990_10 )
- #define TMS99XX_device_get_name "TI990/10"
+ #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( ti990_10l )
+ #define TMS99XX_device_get_name "TI990/10L"
#elif (TMS99XX_MODEL == TMS9900_ID)
#define TMS99XX_PREFIX tms9900
- #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9900 )
- #define TMS99XX_device_get_name "TMS9900"
+ #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9900l )
+ #define TMS99XX_device_get_name "TMS9900L"
#elif (TMS99XX_MODEL == TMS9940_ID)
#define TMS99XX_PREFIX tms9940
- #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9940 )
- #define TMS99XX_device_get_name "TMS9940"
+ #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9940l )
+ #define TMS99XX_device_get_name "TMS9940L"
#error "tms9940 is not yet supported"
#elif (TMS99XX_MODEL == TMS9980_ID)
#define TMS99XX_PREFIX tms9980a
- #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9980a )
- #define TMS99XX_device_get_name "TMS9980A/TMS9981"
+ #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9980al )
+ #define TMS99XX_device_get_name "TMS9980AL/TMS9981L"
#elif (TMS99XX_MODEL == TMS9985_ID)
#define TMS99XX_PREFIX tms9985
- #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9985 )
- #define TMS99XX_device_get_name "TMS9985"
+ #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9985l )
+ #define TMS99XX_device_get_name "TMS9985L"
#error "tms9985 is not yet supported"
#elif (TMS99XX_MODEL == TMS9989_ID)
#define TMS99XX_PREFIX tms9989
- #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9989 )
- #define TMS99XX_device_get_name "TMS9989"
+ #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9989l )
+ #define TMS99XX_device_get_name "TMS9989L"
#error "tms9989 is not yet supported"
#elif (TMS99XX_MODEL == TMS9995_ID)
#define TMS99XX_PREFIX tms9995
- #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9995 )
- #define TMS99XX_device_get_name "TMS9995"
+ #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms9995l )
+ #define TMS99XX_device_get_name "TMS9995L"
#elif (TMS99XX_MODEL == TMS99000_ID)
#define TMS99XX_PREFIX tms99000
- #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms99000 )
- #define TMS99XX_device_get_name "TMS99000"
+ #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms99000l )
+ #define TMS99XX_device_get_name "TMS99000L"
#error "tms99000 is not yet supported"
#elif (TMS99XX_MODEL == TMS99105A_ID)
#define TMS99XX_PREFIX tms99105a
- #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms99105a )
- #define TMS99XX_device_get_name "TMS99105A"
+ #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms99105al )
+ #define TMS99XX_device_get_name "TMS99105AL"
#error "tms99105a is not yet supported"
#elif (TMS99XX_MODEL == TMS99110A_ID)
#define TMS99XX_PREFIX tms99110a
- #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms99110a )
- #define TMS99XX_device_get_name "TMS99110A"
+ #define TMS99XX_GET_INFO CPU_GET_INFO_NAME( tms99110al )
+ #define TMS99XX_device_get_name "TMS99110AL"
#error "tms99110a is not yet supported"
@@ -1485,7 +1485,7 @@ static CPU_EXECUTE( tms99xx )
}
else
{
- logerror("tms9900.c : the interrupt_pending flag was set incorrectly\n");
+ logerror("tms9900l.c : the interrupt_pending flag was set incorrectly\n");
cpustate->interrupt_pending = 0;
}
}
@@ -4769,7 +4769,7 @@ void TMS99XX_GET_INFO(legacy_cpu_device *device, UINT32 state, cpuinfo *info)
/* --- the following bits of info are returned as NULL-terminated strings --- */
case DEVINFO_STR_NAME: strcpy(info->s, TMS99XX_device_get_name); break;
- case DEVINFO_STR_FAMILY: strcpy(info->s, "Texas Instruments 9900"); break;
+ case DEVINFO_STR_FAMILY: strcpy(info->s, "Texas Instruments 9900L"); break;
case DEVINFO_STR_VERSION: strcpy(info->s, "2.0"); break;
case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break;
case DEVINFO_STR_CREDITS: strcpy(info->s, "C TMS9900 emulator by Edward Swartz, initially converted for Mame by M.Coates, updated by R. Nabet"); break;
diff --git a/src/emu/cpu/tms9900/ti990_10.c b/src/emu/cpu/tms9900/ti990_10.c
index c15efba995b..e6e28a88025 100644
--- a/src/emu/cpu/tms9900/ti990_10.c
+++ b/src/emu/cpu/tms9900/ti990_10.c
@@ -1,13 +1,4 @@
/*
- Generate the tms9900 emulator
+ This file will contain the re-implementation of the ti990_10. The
+ previous implementation can be found as ti990_10l.
*/
-
-#include "emu.h"
-#include "debugger.h"
-#include "tms9900.h"
-
-#define TMS99XX_MODEL TI990_10_ID
-
-#include "99xxcore.h"
-
-DEFINE_LEGACY_CPU_DEVICE(TI990_10, ti990_10);
diff --git a/src/emu/cpu/tms9900/ti990_10l.c b/src/emu/cpu/tms9900/ti990_10l.c
new file mode 100644
index 00000000000..4a32fb3c91e
--- /dev/null
+++ b/src/emu/cpu/tms9900/ti990_10l.c
@@ -0,0 +1,13 @@
+/*
+ Generate the tms9900 emulator
+*/
+
+#include "emu.h"
+#include "debugger.h"
+#include "tms9900l.h"
+
+#define TMS99XX_MODEL TI990_10_ID
+
+#include "99xxcore.h"
+
+DEFINE_LEGACY_CPU_DEVICE(TI990_10L, ti990_10l);
diff --git a/src/emu/cpu/tms9900/tms9900.c b/src/emu/cpu/tms9900/tms9900.c
index 7d2c77878fc..f337557dea3 100644
--- a/src/emu/cpu/tms9900/tms9900.c
+++ b/src/emu/cpu/tms9900/tms9900.c
@@ -1,13 +1,2520 @@
/*
- Generate the tms9900 emulator
+ Texas Instruments TMS9900
+
+ +--------------------+
+ V_BB | 1 o 64| /HOLD
+ V_CC | 2 63| /MEMEN
+ WAIT | 3 62| READY
+ /LOAD | 4 61| /WE
+ HOLDA | 5 60| CRUCLK
+ /RESET | 6 59| V_CC
+ IAQ | 7 58| -
+ PHI1 | 8 57| -
+ PHI2 | 9 56| D15 -+ LSB
+ LSB +- A14 |10 55| D14 |
+ | A13 |11 54| D13 |
+ | A12 |12 53| D12 |
+ | A11 |13 52| D11 |
+ Address | A10 |14 +--------+ 51| D10 | Data
+ bus | A9 |15 | | 50| D9 | bus
+ 32K * | A8 |16 | | 49| D8 | 16 bit
+ 16bit | A7 |17 | | 48| D7 |
+ | A6 |18 | | 47| D6 |
+ | A5 |19 +--------+ 46| D5 |
+ | A4 |20 45| D4 |
+ | A3 |21 44| D3 |
+ | A2 |22 43| D2 |
+ | A1 |23 42| D1 |
+ MSB +- A0 |24 41| D0 -+ MSB
+ PHI4 |25 40| V_SS
+ V_SS |26 39| -
+ V_DD |27 38| -
+ PHI3 |28 37| -
+ DBIN |29 36| IC0 -+ MSB
+ CRUOUT |30 35| IC1 | Interrupt
+ CRUIN |31 34| IC2 | level
+ /INTREQ |32 33| IC3 -+ LSB
+ +--------------------+
+
+ WAIT out Processor in wait state
+ /LOAD in Non-maskable interrupt
+ HOLDA out Hold acknowledge
+ /RESET in Reset
+ IAQ out Instruction acquisition
+ PHI1-4 in Clock phase inputs
+ DBIN out Data bus in input mode
+ CRUOUT out Communication register unit data output
+ CRUIN in Communication register unit data input
+ /INTREQ in Interrupt request
+ CRUCLK out Communication register unit clock output
+ /WE out Data available for memory write
+ READY in Memory ready for access
+ /MEMEN out Address bus contains memory address
+ /HOLD in External device acquires address and data bus lines
+
+ V_BB -5V supply
+ V_CC +5V supply (pins 2 and 59 connected in parallel)
+ V_DD +12V supply
+ V_SS 0V Ground reference (pins 26 and 40 connected in parallel)
+
+ A0-A14 out Address bus (32768 words of 16 bit width)
+ D0-A15 i/o Data bus
+ IC0-IC3 in Interrupt level (0-15)
+
+ Note that Texas Instruments' bit numberings define bit 0 as the
+ most significant bit (different to most other systems). Also, the
+ system uses big-endian memory organisation: Storing the word 0x1234 at
+ address 0x0000 means that the byte 0x12 is stored at 0x0000 and byte 0x34
+ is stored at 0x0001.
+
+ The processor also knows byte-oriented operations (like add byte (AB),
+ move byte (MOVB)). This makes it necessary for the CPU to read the word
+ from the target memory location first, change the respective byte, and
+ write it back.
+
+ See the TI-99/4A driver for an application of the TMS9900 processor
+ within an 8-bit data bus board layout (using a data bus multiplexer).
+
+ Subcycle handling
+
+ In this implementation we try to emulate the internal operations as
+ precisely as possible, following the technical specifications. We need
+ not try to be clock-precise with every tick; it suffices to perform
+ the proper number of operations within a given time span.
+
+ For each command the CPU executes a microprogram which requires some
+ amount of cycles to complete. During this time the external clock continues
+ to issue pulses which can be used to control wait state creation. As we
+ do not emulate external clocks this implementation offers an extra output
+ "clock_out" (which, however, is available for the TMS9995) which pulses
+ at a rate of 3 MHz. External devices (e.g. memory controllers) may count
+ the pulses and pull down the READY line (with set_ready) as needed.
+
+ Another possibility for creating wait states is to pull down the line
+ for some time set by a timer. This is done, for example, by circuits like
+ GROMs or speech synthesis processors (TMS52xx).
+
+ TODO:
+ - Fine-tune cycles
+ - State save
+ - HOLD state should be tested; I don't have test cases yet
+
+ Michael Zapf, June 2012
*/
-#include "emu.h"
-#include "debugger.h"
#include "tms9900.h"
-#define TMS99XX_MODEL TMS9900_ID
+/* tms9900 ST register bits. */
+enum
+{
+ ST_LH = 0x8000, // Logical higher (unsigned comparison)
+ ST_AGT = 0x4000, // Arithmetical greater than (signed comparison)
+ ST_EQ = 0x2000, // Equal
+ ST_C = 0x1000, // Carry
+ ST_OV = 0x0800, // Overflow (when using signed operations)
+ ST_OP = 0x0400, // Odd parity (used with byte operations)
+ ST_X = 0x0200, // XOP
+ ST_IM = 0x000f // Interrupt mask
+};
+
+enum
+{
+ LOAD_INT = -1,
+ RESET_INT = -2
+};
+
+#define LOG logerror
+#define VERBOSE 1
+
+/****************************************************************************
+ Constructor
+****************************************************************************/
+
+tms9900_device::tms9900_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : cpu_device(mconfig, TMS9900, "TMS9900", tag, owner, clock),
+ m_program_config("program", ENDIANNESS_BIG, 16, 16),
+ m_io_config("cru", ENDIANNESS_BIG, 8, 16),
+ m_prgspace(NULL),
+ m_cru(NULL)
+{
+}
+
+enum
+{
+ TMS9900_PC=0, TMS9900_WP, TMS9900_STATUS, TMS9900_IR,
+ TMS9900_R0, TMS9900_R1, TMS9900_R2, TMS9900_R3,
+ TMS9900_R4, TMS9900_R5, TMS9900_R6, TMS9900_R7,
+ TMS9900_R8, TMS9900_R9, TMS9900_R10, TMS9900_R11,
+ TMS9900_R12, TMS9900_R13, TMS9900_R14, TMS9900_R15
+};
+
+void tms9900_device::device_start()
+{
+ const tms9900_config *conf = reinterpret_cast<const tms9900_config *>(static_config());
+
+ assert (conf != NULL);
+
+ // TODO: Restore state save feature
+
+ 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_get_ic0123.resolve(conf->irq_level, *this);
+ m_iaq_line.resolve(conf->instruction_acquisition, *this);
+ m_clock_out_line.resolve(conf->clock_out, *this);
+ m_wait_line.resolve(conf->wait_line, *this);
+ m_holda_line.resolve(conf->holda_line, *this);
+
+ // set our instruction counter
+ m_icountptr = &m_icount;
+
+ // add the states for the debugger
+ for (int i=0; i < 20; i++)
+ {
+ // callimport = need to use the state_import method to write to the state variable
+ // callexport = need to use the state_export method to read the state variable
+ state_add(i, s_statename[i], m_state_any).callimport().callexport().formatstr("%04X");
+ }
+ state_add(STATE_GENPC, "curpc", PC).formatstr("%4s").noshow();
+ state_add(STATE_GENFLAGS, "status", m_state_any).callimport().callexport().formatstr("%16s").noshow();
+
+ build_command_lookup_table();
+}
+
+void tms9900_device::device_stop()
+{
+ int k = 0;
+ if (VERBOSE>8) LOG("tms9900: Deleting lookup tables\n");
+ while (m_lotables[k]!=NULL) delete[] m_lotables[k++];
+}
+
+/*
+ TMS9900 hard reset
+ The device reset is just the emulator's trigger for the reset procedure
+ which is invoked via the main loop.
+*/
+void tms9900_device::device_reset()
+{
+ m_reset = true;
+}
+
+const char* tms9900_device::s_statename[20] =
+{
+ "PC ", "WP ", "ST ", "IR ",
+ "R0 ", "R1 ", "R2 ", "R3 ",
+ "R4 ", "R5 ", "R6 ", "R7 ",
+ "R8 ", "R9 ", "R10", "R11",
+ "R12", "R13", "R14", "R15"
+};
+
+/*
+ Write the contents of a register by external input (debugger)
+*/
+void tms9900_device::state_import(const device_state_entry &entry)
+{
+ int index = entry.index();
+ switch (entry.index())
+ {
+ case STATE_GENFLAGS:
+ // no action here; we do not allow import, as the flags are all
+ // bits of the STATUS register
+ break;
+ case TMS9900_PC:
+ PC = (UINT16)m_state_any;
+ break;
+ case TMS9900_WP:
+ WP = (UINT16)m_state_any;
+ break;
+ case TMS9900_STATUS:
+ ST = (UINT16)m_state_any;
+ break;
+ case TMS9900_IR:
+ IR = (UINT16)m_state_any;
+ break;
+ default:
+ // Workspace registers
+ if (index <= TMS9900_R15)
+ write_workspace_register_debug(index-TMS9900_R0, (UINT16)m_state_any);
+ break;
+ }
+}
+
+/*
+ Reads the contents of a register for display in the debugger.
+*/
+void tms9900_device::state_export(const device_state_entry &entry)
+{
+ int index = entry.index();
+ switch (entry.index())
+ {
+ case STATE_GENFLAGS:
+ m_state_any = ST;
+ break;
+ case TMS9900_PC:
+ m_state_any = PC;
+ break;
+ case TMS9900_WP:
+ m_state_any = WP;
+ break;
+ case TMS9900_STATUS:
+ m_state_any = ST;
+ break;
+ case TMS9900_IR:
+ m_state_any = IR;
+ break;
+ default:
+ // Workspace registers
+ if (index <= TMS9900_R15)
+ m_state_any = read_workspace_register_debug(index-TMS9900_R0);
+ break;
+ }
+}
+
+/*
+ state_string_export - export state as a string for the debugger
+*/
+void tms9900_device::state_string_export(const device_state_entry &entry, astring &string)
+{
+ static const char *statestr = "LAECOPX-----IIII";
+ char *flags = string.stringbuffer(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;
+ }
+ }
+}
+
+/**************************************************************************/
+
+UINT16 tms9900_device::read_workspace_register_debug(int reg)
+{
+ int temp = m_icount;
+ UINT16 value = m_prgspace->read_word((WP+(reg<<1)) & 0xfffe);
+ m_icount = temp;
+ return value;
+}
+
+void tms9900_device::write_workspace_register_debug(int reg, UINT16 data)
+{
+ int temp = m_icount;
+ m_prgspace->write_word((WP+(reg<<1)) & 0xfffe, data);
+ m_icount = temp;
+}
+
+const address_space_config *tms9900_device::memory_space_config(address_spacenum spacenum) const
+{
+ switch (spacenum)
+ {
+ case AS_PROGRAM:
+ return &m_program_config;
+
+ case AS_IO:
+ return &m_io_config;
+
+ default:
+ return NULL;
+ }
+}
+
+/**************************************************************************
+ Microprograms for the CPU instructions
+
+ The actions which are specific to the respective instruction are
+ invoked by repeated calls of ALU_xxx; each call increases a state
+ variable so that on the next call, the next part can be processed.
+ This saves us a lot of additional functions.
+**************************************************************************/
+
+/*
+ Define the indices for the micro-operation table. This is done for the sake
+ of a simpler microprogram definition as an UINT8[].
+*/
+enum
+{
+ IAQ = 0,
+ MEMORY_READ,
+ MEMORY_WRITE,
+ REG_READ,
+ REG_WRITE,
+ CRU, // 5
+ DATA_DERIVE,
+ RET,
+ END,
+ ALU_NOP,
+ ALU_CLR, // 10
+ ALU_SETADDR,
+ ALU_ADDONE,
+ ALU_SETADDR_ADDONE,
+ ALU_PCADDR_ADVANCE,
+ ALU_SOURCE, // 15
+ ALU_ADDREG,
+ ALU_IMM,
+ ALU_REG,
+ ALU_F1,
+ ALU_COMP, // 20
+ ALU_F3,
+ ALU_MPY,
+ ALU_DIV,
+ ALU_XOP,
+ ALU_CLR_SWPB, // 25
+ ALU_ABS,
+ ALU_X,
+ ALU_B,
+ ALU_BL,
+ ALU_BLWP, // 30
+ ALU_LDCR,
+ ALU_STCR,
+ ALU_SBZ_SBO,
+ ALU_TB,
+ ALU_JMP, // 35
+ ALU_SHIFT,
+ ALU_AI_ORI,
+ ALU_CI,
+ ALU_LI,
+ ALU_LWPI, // 40
+ ALU_LIMI,
+ ALU_STWP_STST,
+ ALU_EXT,
+ ALU_RTWP,
+ ALU_INT // 45
+};
+
+
+#define MICROPROGRAM(_MP) \
+ static const UINT8 _MP[] =
+
+/*
+ This is a kind of subroutine with 6 variants. Might be done in countless
+ better ways, but will suffice for now. Each variant has at most 8 steps
+ RET will return to the caller.
+ The padding simplifies the calculation of the start address: We just
+ take the Ts field as an index. In the last two cases we add an offset of 8
+ if we have an indexed (resp. a byte) operation.
+*/
+MICROPROGRAM(data_derivation)
+{
+ REG_READ, RET, 0, 0, 0, 0, 0, 0, // Rx (00)
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ REG_READ, ALU_SETADDR, MEMORY_READ, RET, 0, 0, 0, 0, // *Rx (01)
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ ALU_CLR, ALU_PCADDR_ADVANCE, MEMORY_READ, ALU_ADDREG, MEMORY_READ, RET, 0, 0, // @sym (10)
+ REG_READ, ALU_PCADDR_ADVANCE, MEMORY_READ, ALU_ADDREG, MEMORY_READ, RET, 0, 0, // @sym(Rx) (10)
+ REG_READ, ALU_SETADDR_ADDONE, ALU_ADDONE, REG_WRITE, MEMORY_READ, RET, 0, 0, // *Rx+ (word) (11)
+ REG_READ, ALU_SETADDR_ADDONE, REG_WRITE, MEMORY_READ, RET, 0, 0, 0 // *Rx+ (byte) (11)
+};
+
+MICROPROGRAM(f1_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_SOURCE, // Store the word
+ DATA_DERIVE,
+ ALU_F1,
+ MEMORY_WRITE,
+ END
+};
+
+MICROPROGRAM(comp_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_SOURCE,
+ DATA_DERIVE,
+ ALU_COMP,
+ ALU_NOP, // Compare operations do not write back any data
+ END
+};
+
+MICROPROGRAM(f3_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_F3,
+ REG_READ, // We have to distinguish this from the C/CB microprogram above
+ ALU_F3,
+ ALU_NOP, // Compare operations do not write back any data
+ END
+};
+
+MICROPROGRAM(xor_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_F3,
+ REG_READ,
+ ALU_F3,
+ MEMORY_WRITE, // XOR again must write back data
+ END
+};
+
+MICROPROGRAM(mult_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_MPY, // Save the value; put register number in m_regnumber
+ REG_READ,
+ ALU_MPY, // 18 cycles for multiplication
+ REG_WRITE, // Write the high word
+ ALU_MPY, // Get low word, increase m_address
+ MEMORY_WRITE,
+ END
+};
+
+MICROPROGRAM(div_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE, // Get divisor
+ ALU_DIV, // 0 Store divisor and get register number
+ REG_READ, // Read register
+ ALU_DIV, // 1 Check overflow, increase address (or abort here)
+ MEMORY_READ, // Read subsequent word (if reg=15 this is behind the workspace)
+ ALU_DIV, // 2 Calculate quotient (takes variable amount of cycles; at least 32 machine cycles), set register number
+ REG_WRITE, // Write quotient into register
+ ALU_DIV, // 3 Get remainder
+ MEMORY_WRITE, // Write remainder
+ END
+};
+
+MICROPROGRAM(xop_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE, // Get argument
+ ALU_XOP, // 0 Save the address of the source operand, set address = 0x0040 + xopNr*4
+ MEMORY_READ, // Read the new WP
+ ALU_XOP, // 1 Save old WP, set new WP, get the source operand address
+ REG_WRITE, // Write the address of the source operand into the new R11
+ ALU_XOP, // 2
+ REG_WRITE, // Write the ST into the new R15
+ ALU_XOP, // 3
+ REG_WRITE, // Write the PC into the new R14
+ ALU_XOP, // 4
+ REG_WRITE, // Write the WP into the new R13
+ ALU_XOP, // 5 Set the X bit in the ST
+ MEMORY_READ, // Read the new PC
+ ALU_XOP, // 6 Set the new PC
+ ALU_NOP,
+ END
+};
+
+MICROPROGRAM(clr_swpb_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_CLR_SWPB,
+ MEMORY_WRITE,
+ END
+};
+
+MICROPROGRAM(abs_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_ABS, // two cycles
+ MEMORY_WRITE, // skipped when ABS is not performed
+ ALU_NOP,
+ END
+};
+
+MICROPROGRAM(x_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_X,
+ END
+};
+
+MICROPROGRAM(b_mp) // Branch
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_B,
+ END
+};
+
+MICROPROGRAM(bl_mp) // Branch and Link
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_BL, // two cycles
+ REG_WRITE,
+ END
+};
+
+MICROPROGRAM(blwp_mp) // Branch and Load WP
+{
+ ALU_NOP,
+ DATA_DERIVE, // Get argument
+ ALU_BLWP, // 0 Save old WP, set new WP, save position
+ REG_WRITE, // write ST to R15
+ ALU_BLWP, // 1
+ REG_WRITE, // write PC to R14
+ ALU_BLWP, // 2
+ REG_WRITE, // write WP to R13
+ ALU_BLWP, // 3 Get saved position
+ MEMORY_READ, // Read new PC
+ ALU_BLWP, // 4 Set new PC
+ END
+};
+
+MICROPROGRAM(ldcr_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_SOURCE,
+ ALU_NOP,
+ ALU_LDCR,
+ REG_READ,
+ ALU_LDCR,
+ CRU,
+ ALU_NOP,
+ END
+};
+
+MICROPROGRAM(stcr_mp)
+{
+ ALU_NOP,
+ DATA_DERIVE,
+ ALU_SOURCE, // Store address and value
+ ALU_STCR, // 0 Set register_number = 12
+ REG_READ,
+ ALU_STCR, // 1 Prepare CRU access
+ CRU,
+ ALU_STCR, // 2 Create result; Cycles = 5 + (8-#C-1) or + (16-#C)
+ MEMORY_WRITE,
+ END
+};
+
+MICROPROGRAM(sbz_sbo_mp)
+{
+ ALU_SBZ_SBO,
+ REG_READ,
+ ALU_SBZ_SBO,
+ CRU,
+ END
+};
+
+MICROPROGRAM(tb_mp)
+{
+ ALU_TB,
+ REG_READ,
+ ALU_TB,
+ CRU,
+ ALU_TB,
+ END
+};
+
+MICROPROGRAM(jmp_mp)
+{
+ ALU_NOP,
+ ALU_NOP,
+ ALU_JMP,
+ ALU_JMP,
+ END
+};
+
+MICROPROGRAM(shift_mp)
+{
+ ALU_SHIFT,
+ REG_READ,
+ ALU_SHIFT,
+ REG_READ,
+ ALU_SHIFT,
+ REG_WRITE,
+ END
+};
+
+MICROPROGRAM(ai_ori_mp)
+{
+ ALU_REG,
+ REG_READ,
+ ALU_IMM,
+ MEMORY_READ,
+ ALU_AI_ORI,
+ REG_WRITE,
+ END
+};
+
+MICROPROGRAM(ci_mp)
+{
+ ALU_REG,
+ REG_READ,
+ ALU_IMM,
+ MEMORY_READ,
+ ALU_CI,
+ ALU_NOP,
+ END
+};
+
+MICROPROGRAM(li_mp)
+{
+ ALU_IMM,
+ MEMORY_READ,
+ ALU_LI, // sets status bits
+ ALU_REG, // set register number
+ REG_WRITE,
+ END
+};
+
+MICROPROGRAM(lwpi_mp)
+{
+ ALU_IMM,
+ MEMORY_READ,
+ ALU_NOP,
+ ALU_LWPI, // sets WP
+ END
+};
+
+MICROPROGRAM(limi_mp)
+{
+ ALU_IMM,
+ MEMORY_READ,
+ ALU_NOP,
+ ALU_LIMI, // sets interrupt mask in ST
+ ALU_NOP,
+ ALU_NOP,
+ END
+};
+
+MICROPROGRAM(stwp_stst_mp)
+{
+ ALU_STWP_STST,
+ ALU_REG,
+ REG_WRITE,
+ END
+};
+
+MICROPROGRAM(external_mp)
+{
+ ALU_EXT,
+ END
+};
+
+MICROPROGRAM(rtwp_mp) // Problem: This makes RTWP use 8 instead of 7 machine cycles.
+{
+ ALU_RTWP,
+ REG_READ,
+ ALU_RTWP,
+ REG_READ,
+ ALU_RTWP,
+ REG_READ,
+ ALU_RTWP,
+ END
+};
+
+MICROPROGRAM(int_mp)
+{
+ ALU_NOP,
+ ALU_INT, // 0 Set address = 0
+ MEMORY_READ,
+ ALU_INT, // 1 Save old WP, set new WP, save position
+ REG_WRITE, // write ST to R15
+ ALU_INT, // 2
+ REG_WRITE, // write PC to R14
+ ALU_INT, // 3
+ REG_WRITE, // write WP to R13
+ ALU_INT, // 4 Get saved position
+ MEMORY_READ, // Read new PC
+ ALU_INT, // 5 Set new PC
+ END
+};
+
+const tms9900_device::ophandler tms9900_device::s_microoperation[] =
+{
+ &tms9900_device::acquire_instruction,
+ &tms9900_device::mem_read,
+ &tms9900_device::mem_write,
+ &tms9900_device::register_read,
+ &tms9900_device::register_write,
+ &tms9900_device::cru_operation,
+ &tms9900_device::data_derivation_subprogram,
+ &tms9900_device::return_from_subprogram,
+ &tms9900_device::command_completed,
+
+ &tms9900_device::alu_nop,
+ &tms9900_device::alu_clear,
+ &tms9900_device::alu_setaddr,
+ &tms9900_device::alu_addone,
+ &tms9900_device::alu_setaddr_addone,
+ &tms9900_device::alu_pcaddr_advance,
+ &tms9900_device::alu_source,
+ &tms9900_device::alu_add_register,
+ &tms9900_device::alu_imm,
+ &tms9900_device::alu_reg,
+
+ &tms9900_device::alu_f1,
+ &tms9900_device::alu_comp,
+ &tms9900_device::alu_f3,
+ &tms9900_device::alu_multiply,
+ &tms9900_device::alu_divide,
+ &tms9900_device::alu_xop,
+ &tms9900_device::alu_clr_swpb,
+ &tms9900_device::alu_abs,
+ &tms9900_device::alu_x,
+ &tms9900_device::alu_b,
+ &tms9900_device::alu_bl,
+ &tms9900_device::alu_blwp,
+ &tms9900_device::alu_ldcr,
+ &tms9900_device::alu_stcr,
+ &tms9900_device::alu_sbz_sbo,
+ &tms9900_device::alu_tb,
+ &tms9900_device::alu_jmp,
+ &tms9900_device::alu_shift,
+ &tms9900_device::alu_ai_ori,
+ &tms9900_device::alu_ci,
+ &tms9900_device::alu_li,
+ &tms9900_device::alu_lwpi,
+ &tms9900_device::alu_limi,
+ &tms9900_device::alu_stwp_stst,
+ &tms9900_device::alu_external,
+ &tms9900_device::alu_rtwp,
+ &tms9900_device::alu_int
+};
+
+/*****************************************************************************
+ CPU instructions
+*****************************************************************************/
+
+/*
+ Available instructions
+*/
+enum
+{
+ ILL=0, A, AB, ABS, AI, ANDI, B, BL, BLWP, C,
+ CB, CI, CKOF, CKON, CLR, COC, CZC, DEC, DECT, DIV,
+ IDLE, INC, INCT, INV, JEQ, JGT, JH, JHE, JL, JLE,
+ JLT, JMP, JNC, JNE, JNO, JOC, JOP, LDCR, LI, LIMI,
+ LREX, LWPI, MOV, MOVB, MPY, NEG, ORI, RSET, RTWP, S,
+ SB, SBO, SBZ, SETO, SLA, SOC, SOCB, SRA, SRC, SRL,
+ STCR, STST, STWP, SWPB, SZC, SZCB, TB, X, XOP, XOR
+};
+
+static const char opname[][5] =
+{ "ILL ", "A ", "AB ", "ABS ", "AI ", "ANDI", "B ", "BL ", "BLWP", "C ",
+ "CI ", "CB ", "CKOF", "CKON", "CLR ", "COC ", "CZC ", "DEC ", "DECT", "DIV ",
+ "IDLE", "INC ", "INCT", "INV ", "JEQ ", "JGT ", "JH ", "JHE ", "JL ", "JLE ",
+ "JLT ", "JMP ", "JNC ", "JNE ", "JNO ", "JOC ", "JOP ", "LDCR", "LI ", "LIMI",
+ "LREX", "LWPI", "MOV ", "MOVB", "MPY ", "NEG ", "ORI ", "RSET", "RTWP", "S ",
+ "SB ", "SBO ", "SBZ ", "SETO", "SLA ", "SOC ", "SOCB", "SRA ", "SRC ", "SRL ",
+ "STCR", "STST", "STWP", "SWPB", "SZC ", "SZCB", "TB ", "X ", "XOP ", "XOR "
+};
+
+/*
+ Formats:
+
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ----+------------------------------------------------+
+ 1 | Opcode | B | Td | RegNr | Ts | RegNr |
+ +--------+---+----+------------+----+------------+
+ 2 | Opcode | Displacement |
+ +-----------------------+------------------------+
+ 3 | Opcode | RegNr | Ts | RegNr |
+ +-----------------+------------+----+------------+
+ 4 | Opcode | Count | Ts | RegNr |
+ +-----------------+------------+----+------------+
+ 5 | Opcode | Count | RegNr |
+ +-----------------------+-----------+------------+
+ 6 | Opcode | Ts | RegNr |
+ +------------------------------+----+------------+
+ 7 | Opcode |0| 0| 0| 0| 0 |
+ +---------------------------------+-+--+--+--+---+
+ 8 | Opcode |0| RegNr |
+ +---------------------------------+-+------------+
+ 9 | Opcode | Reg/Nr | Ts | RegNr |
+ +-----------------+------------+----+------------+
+*/
+
+/*
+ Defines the number of bits from the left which are significant for the
+ command in the respective format.
+*/
+static const int format_mask_len[] =
+{
+ 0, 4, 8, 6, 6, 8, 10, 16, 12, 6
+};
+
+const tms9900_device::tms_instruction tms9900_device::s_command[] =
+{
+ // Opcode, ID, format, microprg
+ { 0x0200, LI, 8, li_mp },
+ { 0x0220, AI, 8, ai_ori_mp },
+ { 0x0240, ANDI, 8, ai_ori_mp },
+ { 0x0260, ORI, 8, ai_ori_mp },
+ { 0x0280, CI, 8, ci_mp },
+ { 0x02a0, STWP, 8, stwp_stst_mp },
+ { 0x02c0, STST, 8, stwp_stst_mp },
+ { 0x02e0, LWPI, 8, lwpi_mp },
+ { 0x0300, LIMI, 8, limi_mp },
+ { 0x0340, IDLE, 7, external_mp },
+ { 0x0360, RSET, 7, external_mp },
+ { 0x0380, RTWP, 7, rtwp_mp },
+ { 0x03a0, CKON, 7, external_mp },
+ { 0x03c0, CKOF, 7, external_mp },
+ { 0x03e0, LREX, 7, external_mp },
+ { 0x0400, BLWP, 6, blwp_mp },
+ { 0x0440, B, 6, b_mp },
+ { 0x0480, X, 6, x_mp },
+ { 0x04c0, CLR, 6, clr_swpb_mp },
+ { 0x0500, NEG, 6, clr_swpb_mp },
+ { 0x0540, INV, 6, clr_swpb_mp },
+ { 0x0580, INC, 6, clr_swpb_mp },
+ { 0x05c0, INCT, 6, clr_swpb_mp },
+ { 0x0600, DEC, 6, clr_swpb_mp },
+ { 0x0640, DECT, 6, clr_swpb_mp },
+ { 0x0680, BL, 6, bl_mp },
+ { 0x06c0, SWPB, 6, clr_swpb_mp },
+ { 0x0700, SETO, 6, clr_swpb_mp },
+ { 0x0740, ABS, 6, abs_mp },
+ { 0x0800, SRA, 5, shift_mp },
+ { 0x0900, SRL, 5, shift_mp },
+ { 0x0a00, SLA, 5, shift_mp },
+ { 0x0b00, SRC, 5, shift_mp },
+ { 0x1000, JMP, 2, jmp_mp },
+ { 0x1100, JLT, 2, jmp_mp },
+ { 0x1200, JLE, 2, jmp_mp },
+ { 0x1300, JEQ, 2, jmp_mp },
+ { 0x1400, JHE, 2, jmp_mp },
+ { 0x1500, JGT, 2, jmp_mp },
+ { 0x1600, JNE, 2, jmp_mp },
+ { 0x1700, JNC, 2, jmp_mp },
+ { 0x1800, JOC, 2, jmp_mp },
+ { 0x1900, JNO, 2, jmp_mp },
+ { 0x1a00, JL, 2, jmp_mp },
+ { 0x1b00, JH, 2, jmp_mp },
+ { 0x1c00, JOP, 2, jmp_mp },
+ { 0x1d00, SBO, 2, sbz_sbo_mp },
+ { 0x1e00, SBZ, 2, sbz_sbo_mp },
+ { 0x1f00, TB, 2, tb_mp },
+ { 0x2000, COC, 3, f3_mp },
+ { 0x2400, CZC, 3, f3_mp },
+ { 0x2800, XOR, 3, xor_mp },
+ { 0x2c00, XOP, 3, xop_mp },
+ { 0x3000, LDCR, 4, ldcr_mp },
+ { 0x3400, STCR, 4, stcr_mp },
+ { 0x3800, MPY, 9, mult_mp },
+ { 0x3c00, DIV, 9, div_mp },
+ { 0x4000, SZC, 1, f1_mp },
+ { 0x5000, SZCB, 1, f1_mp },
+ { 0x6000, S, 1, f1_mp },
+ { 0x7000, SB, 1, f1_mp },
+ { 0x8000, C, 1, comp_mp },
+ { 0x9000, CB, 1, comp_mp },
+ { 0xa000, A, 1, f1_mp },
+ { 0xb000, AB, 1, f1_mp },
+ { 0xc000, MOV, 1, f1_mp },
+ { 0xd000, MOVB, 1, f1_mp },
+ { 0xe000, SOC, 1, f1_mp },
+ { 0xf000, SOCB, 1, f1_mp }
+};
+
+/*
+ Create a B-tree for looking up the commands. Each node can carry up to
+ 16 entries, indexed by 4 consecutive bits in the opcode.
+
+ Works as follows:
+
+ Opcode = 0201 (Load immediate value into register 1)
+ Opcode = 0284 (Compare immediate value with register 4)
+
+ Table: [ Table0, table1, table2, ... tableF ]
+ |
+ +-------+
+ v
+ table0: [ table00, table01, table02, ... table0f ]
+ |
+ +-------------------------+
+ v
+ table02: [ table020, table021, ... table028, ... table02f ]
+ | | |
+ v v v
+ Entry NULL Entry
+ for LI for CI
+
+ For each level in the tree, four more bits are compared. The search
+ terminates when the number of compared bits is equal or higher than
+ the number of significant bits of the format of this opcode. The entry
+ points to the respective line in s_command.
+
+ This way we can decode all format 1 commands by a single pass (including the
+ most frequent command MOV), and almost all commands by less than four passes.
+
+ The disadvantage is that we have to build these tables from the opcode
+ list at runtime, and many positions are empty. But we do not need more
+ than 20 tables for the TMS command set.
+*/
+void tms9900_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("tms9900: === 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("tms9900: 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("tms9900: 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("tms9900: next index=%x\n", cmdindex);
+ }
+
+ if (VERBOSE>8) LOG("tms9900: 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("tms9900: 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("tms9900: Allocated %d tables\n", k);
+}
+
+/*
+ Main execution loop
+
+ For each invocation of execute_run, a number of loop iterations has been
+ calculated before (m_icount). Each loop iteration is one clock cycle.
+ The loop must be executed for the number of times that corresponds to the
+ time until the next timer event.
+
+ In this implementation, each loop iteration also causes the clock line to
+ pulse once. External devices may use this pulse to decrement counters
+ which control the READY line.
+
+ Machine cycles to clock input:
+
+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
+ | | | | | | | | | | | | | | | | | | clock (1 of 4 phases)
+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +
+ |-------|-------|-------|-------|---- cycles (2 clock pulses each)
+
+ Wait states only have effect for memory operations. They are processed as
+ follows:
+
+ 1) The CPU sets the address bus for reading. If READY is low, the CPU
+ waits for the next clock tick repeatedly until READY is high again.
+ When this is the case, the data bus is sampled on the next clock tick
+ and the read operation is complete.
+
+ As we do not have a split-phase read operation in this emulation
+ we actually read the data bus instantly but wait for the READY line to
+ be high again.
+
+ 2) The CPU sets the address bus for writing. In the same moment, the data
+ bus is loaded with the word to be written. On the next clock tick,
+ the CPU checks the READY line and waits until it is high. When READY
+ is high at a clock tick, the operation is complete on the next clock tick.
+*/
+void tms9900_device::execute_run()
+{
+ if (m_reset) service_interrupt();
+
+ if (VERBOSE>6) LOG("tms9900: calling execute_run for %d cycles\n", m_icount);
+ do
+ {
+ // Only when last instruction has completed
+ if (m_program == NULL)
+ {
+ if (m_load_state)
+ {
+ if (VERBOSE>4) LOG("tms9900: load interrupt\n");
+ service_interrupt();
+ }
+ else
+ {
+ // Interrupts are serviced when
+ // - an interrupt condition is signaled over INTREQ and
+ // - the level indicated by IC0-IC3 is lower than the interrupt mask value and
+ // - the previous instruction is not an XOP or BLWP
+ if (m_irq_state && (m_irq_level <= (ST & 0x000f)) && (m_command != XOP && m_command != BLWP))
+ service_interrupt();
+ }
+ }
+
+ if (m_program == NULL && m_idle_state)
+ {
+ if (VERBOSE>5) LOG("tms9900: idle state\n");
+ pulse_clock(1);
+ }
+ else
+ {
+ // Handle HOLD
+ // A HOLD request is signalled through the input line HOLD.
+ // The hold state will be entered with the next non-memory access cycle.
+ if (m_hold_state &&
+ (m_program==NULL ||
+ (m_program[MPC] != IAQ &&
+ m_program[MPC] != MEMORY_READ && m_program[MPC] != MEMORY_WRITE &&
+ m_program[MPC] != REG_READ && m_program[MPC] != REG_WRITE)))
+ {
+ if (VERBOSE>5) LOG("tms9900: hold state\n");
+ if (!m_hold_acknowledged) acknowledge_hold();
+ pulse_clock(1);
+ }
+ else
+ {
+ // Normal operation
+ if (m_check_ready && m_ready_state == false)
+ {
+ // We are in a wait state
+ set_wait_state(true);
+ if (VERBOSE>5) LOG("tms9900: 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
+ {
+ set_wait_state(false);
+ m_check_ready = false;
+
+ if (m_program==NULL) m_op = IAQ;
+ else
+ {
+ m_op = m_program[MPC];
+ m_iaq_line(CLEAR_LINE);
+ }
+ if (VERBOSE>8) LOG("tms9900: MPC = %d, m_op = %d\n", MPC, m_op);
+ // Call the operation of the microprogram
+ (this->*s_microoperation[m_op])();
+ MPC++;
+ }
+ }
+ }
+ } while (m_icount>0);
+ if (VERBOSE>6) LOG("tms9900: cycles expired; will return soon.\n");
+}
+
+/**************************************************************************/
+
+/*
+ Interrupt input
+*/
+void tms9900_device::execute_set_input(int irqline, int state)
+{
+ if (irqline == INPUT_LINE_NMI)
+ {
+ m_load_state = (state==ASSERT_LINE);
+ m_irq_level = -1;
+ }
+ else
+ {
+ m_irq_state = (state==ASSERT_LINE);
+ if (state==ASSERT_LINE)
+ {
+ m_irq_level = m_get_ic0123(0);
+ if (VERBOSE>6) LOG("tms9900: interrupt line %d = %d, level=%d, ST=%04x\n", irqline, state, m_irq_level, ST);
+ }
+ else
+ {
+ if (VERBOSE>6) LOG("tms9900: cleared interrupt line %d\n", irqline);
+ }
+ }
+}
+
+void tms9900_device::service_interrupt()
+{
+ m_program = int_mp;
+ MPC = 0;
+ m_idle_state = false;
+ m_first_cycle = m_icount;
+ m_state = 0;
+ if (m_reset)
+ {
+ m_ready_state = true;
+ m_load_state = false;
+ m_hold_state = false;
+ m_hold_acknowledged = false;
+ m_wait_state = false;
+ IR = 0;
+ ST = 0;
+
+ m_reset = false;
+ m_irq_level = RESET_INT;
+ }
+ if (VERBOSE>6) LOG("tms9900: ********* triggered an interrupt on level %d\n", m_irq_level);
+}
+
+/*
+ Issue a pulse on the clock line.
+*/
+inline void tms9900_device::pulse_clock(int count)
+{
+ for (int i=0; i < count; i++)
+ {
+ m_clock_out_line(ASSERT_LINE);
+ m_clock_out_line(CLEAR_LINE);
+ m_icount--; // This is the only location where we count down the cycles.
+ if (VERBOSE>7) LOG("tms9900: pulse_clock\n");
+ }
+}
+
+/*
+ Enter the hold state.
+*/
+void tms9900_device::set_hold(int state)
+{
+ m_hold_state = (state==ASSERT_LINE);
+ if (!m_hold_state)
+ {
+ m_hold_acknowledged = false;
+ m_holda_line(CLEAR_LINE);
+ }
+}
+
+/*
+ Acknowledge the HOLD request.
+*/
+inline void tms9900_device::acknowledge_hold()
+{
+ m_hold_acknowledged = true;
+ m_holda_line(ASSERT_LINE);
+}
+
+/*
+ Signal READY to the CPU. When cleared, the CPU enters wait states.
+*/
+void tms9900_device::set_ready(int state)
+{
+ m_ready_state = (state==ASSERT_LINE);
+}
+
+void tms9900_device::abort_operation()
+{
+ command_completed();
+}
+
+/*
+ Enter or leave the wait state. We only operate the WAIT line when there is a change.
+*/
+inline void tms9900_device::set_wait_state(bool state)
+{
+ if (m_wait_state != state) m_wait_line(state? ASSERT_LINE : CLEAR_LINE);
+ m_wait_state = state;
+}
+
+/*
+ Memory read:
+ 1) Pulse clock (done above)
+ 2) Set address (we also get the value right here)
+ 3) Pulse clock
+ 4) If READY=L (WAIT=H, GOTO 3) else (WAIT=L, STOP)
+
+ Clock cycles: 2 + W, W = number of wait states
+*/
+UINT16 tms9900_device::pulse_and_read_memory(UINT16 address)
+{
+ UINT16 value;
+ value = m_prgspace->read_word(address & 0xfffe);
+ pulse_clock(2);
+ m_check_ready = true;
+ return value;
+}
+
+/*
+ Memory write:
+ 1) Pulse clock
+ 2) Set address and write (as in the real system)
+ 3) Pulse clock
+ 4) If READY=L (WAIT=H, GOTO 3) else (WAIT=L, STOP)
+
+ Clock cycles: 2 + W, W = number of wait states
+*/
+void tms9900_device::pulse_and_write_memory(UINT16 address, UINT16 data)
+{
+ m_prgspace->write_word(address & 0xfffe, data);
+ pulse_clock(2);
+ m_check_ready = true;
+}
+
+
+/*
+ Acquire the next word as an instruction. The program counter advances by
+ one word.
+*/
+void tms9900_device::decode(UINT16 inst)
+{
+ int index = 0;
+ lookup_entry* table = m_command_lookup_table;
+ UINT16 opcode = inst;
+ bool complete = false;
+ const tms_instruction *decoded;
+
+ m_state = 0;
+ IR = inst;
+ m_get_destination = false;
+
+ while (!complete)
+ {
+ index = (opcode >> 12) & 0x000f;
+ if (VERBOSE>8) LOG("tms9900: 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("tms9900: Illegal opcode %04x\n", inst);
+ IR = 0;
+ // This will cause another instruction acquisition in the next machine cycle
+ // with an asserted IAQ line (can be used to detect this illegal opcode detection).
+ m_program = NULL;
+ }
+ else
+ {
+ m_program = decoded->prog;
+ MPC = -1;
+ m_command = decoded->id;
+ if (VERBOSE>7) LOG("tms9900: Command decoded as id %d, %s, base opcode %04x\n", m_command, opname[m_command], decoded->opcode);
+ }
+}
+
+inline bool tms9900_device::byte_operation()
+{
+ return (IR & 0x1000)!=0;
+}
+
+void tms9900_device::acquire_instruction()
+{
+ m_iaq_line(ASSERT_LINE);
+ m_address = PC;
+ m_first_cycle = m_icount;
+ mem_read();
+ decode(m_current_value);
+ if (VERBOSE>3) LOG("tms9900: ===== Next operation %04x (%s) at %04x =====\n", IR, opname[m_command], PC);
+ debugger_instruction_hook(*this, PC);
+ PC = (PC + 2) & 0xfffe;
+ // IAQ will be cleared in the main loop
+}
+
+void tms9900_device::mem_read()
+{
+ m_current_value = pulse_and_read_memory(m_address);
+ if (VERBOSE>7) LOG("tms9900: memory read %04x -> %04x\n", m_address, m_current_value);
+}
+
+void tms9900_device::mem_write()
+{
+ pulse_and_write_memory(m_address, m_current_value);
+ if (VERBOSE>7) LOG("tms9900: memory write %04x <- %04x\n", m_address, m_current_value);
+}
+
+void tms9900_device::register_read()
+{
+ // Need to set m_address for F1/F3 (we don't know what the data_derive did)
+ m_address = WP + (m_regnumber<<1);
+ m_register_contents = m_current_value = pulse_and_read_memory(m_address);
+ if (VERBOSE>7) LOG("tms9900: register %d read (mem %04x) -> %04x\n", m_regnumber, m_address, m_current_value);
+}
+
+void tms9900_device::register_write()
+{
+ pulse_and_write_memory(WP + (m_regnumber<<1), m_current_value);
+ if (VERBOSE>7) LOG("tms9900: register %d write (mem %04x) <- %04x\n", m_regnumber, WP + (m_regnumber<<1), m_current_value);
+}
+
+/*
+ CRU support code
+
+ The CRU bus is a 1-bit-wide I/O bus. The CPU can read or write bits at random address.
+ Special instructions are dedicated to reading and writing one or several consecutive bits.
+
+ The CRU uses the same address bus as the normal memory access. For writing,
+ the CRUCLK line is pulsed, but not for reading where CRUCLK stays cleared.
+ This means that each normal memory access also causes read accesses on the
+ CRU side. The /MEMEN line may be used to distinguish the kinds of accesses
+ as it stays cleared during CRU operations.
+
+ We do not emulate this here as it seems there are no real applications of
+ this side effect. Real designs must ensure that CRU read operations are
+ idempotent (i.e. they must not change the state of the queried device).
+
+ Read returns the number of consecutive CRU bits, with increasing CRU address
+ from the least significant to the most significant bit; right-aligned
+
+ There seems to be no handling of wait states during CRU operations on the
+ TMS9900. The TMS9995, in contrast, respects wait states during the transmission
+ of each single bit.
+
+ Usage of this method:
+ CRU write: First bit is at rightmost position of m_value.
+*/
+
+
+#define CRUREADMASK 0x01ff
+#define CRUWRITEMASK 0x0fff
+
+void tms9900_device::cru_operation()
+{
+ int value, value1;
+ int offset, location;
+
+ if (m_cru_output)
+ {
+ location = (m_cru_address >> 1)& CRUWRITEMASK;
+ value = m_value;
+
+ // Write m_count bits from cru_address
+ for (int i=0; i < m_count; i++)
+ {
+ m_cru->write_byte(location, (value & 0x01));
+ value >>= 1;
+ location = (location + 1) & CRUWRITEMASK;
+ pulse_clock(2);
+ }
+ }
+ else
+ {
+ location = (m_cru_address >> 4) & CRUREADMASK;
+ offset = (m_cru_address>>1) & 0x07;
+
+ // Read 8 bits (containing the desired bits)
+ value = m_cru->read_byte(location);
+
+ if ((offset + m_count) > 8) // spans two 8 bit cluster
+ {
+ // Read next 8 bits
+ location = (location + 1) & CRUREADMASK;
+ value1 = m_cru->read_byte(location);
+ value |= (value1 << 8);
+
+ if ((offset + m_count) > 16) // spans three 8 bit cluster
+ {
+ // Read next 8 bits
+ location = (location + 1) & CRUREADMASK;
+ value1 = m_cru->read_byte(location);
+ value |= (value1 << 16);
+ }
+ }
+
+ // On each machine cycle (2 clocks) only one CRU bit is transmitted
+ pulse_clock(m_count<<1);
+
+ // Shift back the bits so that the first bit is at the rightmost place
+ m_value = (value >> offset);
+
+ // Mask out what we want
+ m_value &= (0x0000ffff >> (16-m_count));
+ }
+}
+
+void tms9900_device::return_from_subprogram()
+{
+ // Return from data derivation
+ // The result should be in m_current_value
+ // and the address in m_address
+ m_program = m_caller;
+ MPC = m_caller_MPC; // will be increased on return
+}
+
+void tms9900_device::command_completed()
+{
+ // Pseudo state at the end of the current instruction cycle sequence
+ if (VERBOSE>7)
+ {
+ int cycles = m_first_cycle - m_icount;
+ // Avoid nonsense values due to expired and resumed main loop
+ if (cycles > 0 && cycles < 10000) LOG("tms9900: Instruction %04x (%s) consumed %d cycles\n", IR, opname[m_command], cycles);
+ }
+ m_program = NULL;
+}
+
+/*
+ This is a switch to a subprogram; there is only one, the data
+ derivation. In terms of cycles, it does not take any time; execution
+ continues with the first instruction of the subprogram.
+*/
+void tms9900_device::data_derivation_subprogram()
+{
+ UINT16 ircopy = IR;
+
+ // Save the return program and position
+ m_caller = m_program;
+ m_caller_MPC = MPC;
+
+ // Source or destination argument?
+ if (m_get_destination) ircopy >>= 6;
+
+ m_regnumber = ircopy & 0x000f;
+
+ m_program = (UINT8*)data_derivation;
+ MPC = ircopy & 0x0030;
+
+ if (((MPC == 0x0020) && (m_regnumber != 0)) // indexed
+ || ((MPC == 0x0030) && ((IR & 0x1000)!=0))) // byte flag
+ {
+ MPC += 8; // the second option
+ }
+ m_get_destination = true; // when we call this the second time before END it's the destination
+ MPC--; // will be increased on return
+}
+
+
+/**************************************************************************
+ Status bit operations
+**************************************************************************/
+
+inline void tms9900_device::set_status_bit(int bit, bool state)
+{
+ if (state) ST |= bit;
+ else ST &= ~bit;
+}
+
+void tms9900_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 tms9900_device::compare_and_set_lae(UINT16 value1, UINT16 value2)
+{
+ set_status_bit(ST_EQ, value1 == value2);
+ set_status_bit(ST_LH, value1 > value2);
+ set_status_bit(ST_AGT, (INT16)value1 > (INT16)value2);
+ if (VERBOSE>7) LOG("tms9900: ST = %04x (val1=%04x, val2=%04x)\n", ST, value1, value2);
+}
+
+/**************************************************************************
+ ALU operations
+**************************************************************************/
+
+void tms9900_device::alu_nop()
+{
+ // Do nothing (or nothing that is externally visible)
+ pulse_clock(2);
+ return;
+}
+
+void tms9900_device::alu_source()
+{
+ // Copy the current value into the source data register
+ m_source_even = ((m_address & 1)==0);
+ m_source_value = m_current_value;
+ m_source_address = m_address;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_clear()
+{
+ // Clears the register contents
+ m_register_contents = 0;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_setaddr()
+{
+ // Load the current value into the address register
+ m_address = m_current_value;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_addone()
+{
+ m_current_value++;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_setaddr_addone()
+{
+ // Set the address register and increase the recent value
+ m_address = m_current_value;
+ m_current_value++;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_pcaddr_advance()
+{
+ // Set PC as new read address, increase by 2
+ m_address = PC;
+ PC = (PC+2)&0xfffe;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_add_register()
+{
+ // Add the register contents to the current value and set as address
+ m_address = m_current_value + m_register_contents;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_imm()
+{
+ if (VERBOSE>7) LOG("tms9900: Immediate operand, reading from position %04x\n", PC);
+ m_address = PC;
+ PC = (PC + 2) & 0xfffe;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_reg()
+{
+ m_regnumber = IR & 0x000f;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_f1()
+{
+ UINT32 dest_new = 0;
+
+ // Save the destination value
+ UINT16 prev_dest_value = m_current_value;
+
+ m_destination_even = ((m_address & 1)==0); // this is the destination address; the source address has already been saved
+ bool byteop = byte_operation();
+
+ if (byteop)
+ {
+ if (!m_destination_even) m_current_value <<= 8;
+ if (!m_source_even) m_source_value <<= 8;
+ // We have to strip away the low byte, or byte operations may fail
+ // e.g. 0x10ff + 0x0101 = 0x1200
+ // or 0x2000 - 0x0101 = 0x1eff
+ m_source_value &= 0xff00;
+ m_current_value &= 0xff00;
+ }
+
+ switch (m_command)
+ {
+ case A:
+ case AB:
+ // Add the contents of the source data to the destination data
+ // May exceed 0xffff (for carry check)
+ dest_new = m_current_value + m_source_value;
+
+ // 1000 + e000 = f000 (L)
+ // c000 + c000 = 8000 (LC)
+ // 7000 + 4000 = b000 (LO)
+ // 2000 + f000 = 1000 (LAC)
+ // c000 + b000 = 7000 (LACO)
+ // 2000 + e000 = 0000 (EC)
+ // 8000 + 8000 = 0000 (ECO)
+
+ // When adding, a carry occurs when we exceed the 0xffff value.
+ set_status_bit(ST_C, (dest_new & 0x10000) != 0);
+ // If the result has a sign bit that is different from both arguments, we have an overflow
+ // (i.e. getting a negative value from two positive values and vice versa)
+ set_status_bit(ST_OV, ((dest_new ^ m_current_value) & (dest_new ^ m_source_value) & 0x8000)!=0);
+ break;
+
+ case S:
+ case SB:
+ // Subtract the contents of the source data from the destination data
+ dest_new = m_current_value + ((~m_source_value) & 0xffff) + 1;
+ // LAECO(P)
+ // 8000 - 8000 = 0000 (EC)
+ // 2000 - 8000 = a000 (LO)
+ // 8000 - 2000 = 6000 (LACO)
+ // 2000 - 1000 = 1000 (LAC)
+ // 1000 - 2000 = f000 (L)
+ // 1000 - 1000 = 0000 (EC)
+ // 1000 - f000 = 2000 (LA)
+ // f000 - 2000 = d000 (LC)
+
+ // Subtraction means adding the 2s complement, so the carry bit
+ // is set whenever adding the 2s complement exceeds ffff
+ // In fact the CPU adds the one's complement, then adds a one. This
+ // explains why subtracting 0 sets the carry bit.
+ set_status_bit(ST_C, (dest_new & 0x10000) != 0);
+
+ // If the arguments have different sign bits and the result has a
+ // sign bit different from the destination value, we have an overflow
+ // e.g. value1 = 0x7fff, value2 = 0xffff; value1-value2 = 0x8000
+ // or value1 = 0x8000, value2 = 0x0001; value1-value2 = 0x7fff
+ // value1 is the destination value
+ set_status_bit(ST_OV, (m_current_value ^ m_source_value) & (m_current_value ^ dest_new) & 0x8000);
+ break;
+
+ case SOC:
+ case SOCB:
+ // OR the contents of the source data on the destination data
+ dest_new = m_current_value | m_source_value;
+ break;
+
+ case SZC:
+ case SZCB:
+ // AND the one's complement of the contents of the source data on the destination data
+ dest_new = m_current_value & ~m_source_value;
+ break;
+
+ case MOV:
+ case MOVB:
+ // Copy the source data to the destination data
+ dest_new = m_source_value;
+ break;
+ }
+
+ if (byteop)
+ {
+ set_status_parity((UINT8)(dest_new>>8));
+
+ // destnew is the new value to be written (high byte); needs to be
+ // merged with the existing word
+ if (m_destination_even)
+ m_current_value = (prev_dest_value & 0x00ff) | (dest_new & 0xff00);
+ else
+ m_current_value = (prev_dest_value & 0xff00) | ((dest_new >> 8) & 0x00ff);
+ compare_and_set_lae((UINT16)(dest_new & 0xff00), 0);
+ }
+ else
+ {
+ m_current_value = (UINT16)(dest_new & 0xffff);
+ compare_and_set_lae((UINT16)(dest_new & 0xffff), 0);
+ }
+
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_comp()
+{
+ m_destination_even = ((m_address & 1)==0); // this is the destination address; the source address has already been saved
+ if (byte_operation())
+ {
+ if (!m_destination_even) m_current_value <<= 8;
+ if (!m_source_even) m_source_value <<= 8;
+ set_status_parity((UINT8)(m_source_value>>8));
+ compare_and_set_lae(m_source_value & 0xff00, m_current_value & 0xff00);
+ }
+ else
+ compare_and_set_lae(m_source_value, m_current_value);
+
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_f3()
+{
+ if (m_state==0)
+ {
+ m_regnumber = (IR >> 6) & 0x000f;
+ m_source_value = m_current_value;
+ }
+ else
+ {
+ if (VERBOSE>7) LOG("tms9900: coc/czc/xor %04x with %04x\n", m_source_value, m_register_contents);
+ switch (m_command)
+ {
+ case COC:
+ set_status_bit(ST_EQ, (m_register_contents & m_source_value) == m_source_value);
+ break;
+ case CZC:
+ set_status_bit(ST_EQ, (~m_register_contents & m_source_value) == m_source_value);
+ break;
+ case XOR:
+ m_current_value = (m_register_contents ^ m_source_value);
+ compare_and_set_lae(m_current_value, 0);
+ break;
+ }
+ }
+ if (VERBOSE>7) LOG("tms9900: ST = %04x (log comp)\n", ST);
+
+ m_state++;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_multiply()
+{
+ switch (m_state)
+ {
+ case 0: // After data derivation
+ m_source_value = m_current_value;
+ m_regnumber = (IR >> 6)&0x000f;
+ break;
+ case 1: // After reading the register (multiplier)
+ if (VERBOSE>7) LOG("tms9900: Multiply %04x by %04x\n", m_register_contents, m_source_value);
+ m_value = m_register_contents * m_source_value;
+ m_current_value = (m_value >> 16) & 0xffff;
+ m_value = m_value & 0xffff; // Save low word in m_value
+ pulse_clock(34); // add 36 clock cycles (18 machine cycles); last one in main loop
+ break;
+ case 2: // After writing the high word to the destination register
+ m_current_value = m_value; // Prepare to save low word
+ m_address = (m_address + 2) & 0xffff;
+ break;
+ }
+ pulse_clock(2);
+ m_state++;
+}
+
+void tms9900_device::alu_divide()
+{
+ // Format is DIV Divisor,REG(dividend)
+ UINT32 value1;
+
+ switch (m_state)
+ {
+ case 0:
+ m_source_value = m_current_value; // store divisor
+ m_regnumber = (IR >> 6) & 0x000f;
+ break;
+ case 1:
+ // We have an overflow when the quotient cannot be stored in 16 bits
+ // This is the case when the dividend / divisor >= 0x10000,
+ // or equivalently, dividend / 0x10000 >= divisor
+ if (m_register_contents >= m_source_value)
+ {
+ ST |= ST_OV;
+ if (VERBOSE>7) LOG("tms9900: Aborting division because the dividend %08x is too big for the divisor %04x\n", (m_register_contents<<16), m_source_value);
+ abort_operation(); // behaves as if the end of the operation sequence has been reached
+ }
+ else
+ {
+ ST &= ~ST_OV;
+ // m_address is the address of the high word of the dividend (from the previous register read)
+ m_address = (m_address + 2) & 0xffff;
+ }
+ break;
+ case 2:
+ // Create full word and perform division
+ value1 = (m_register_contents << 16) | m_current_value;
+ if (VERBOSE>7) LOG("tms9900: Dividing %08x by %04x\n", value1, m_source_value);
+ m_current_value = (value1 / m_source_value) & 0xffff;
+ m_value = (value1 % m_source_value) & 0xffff;
+ if (VERBOSE>7) LOG("tms9900: Quotient %04x, remainder %04x\n", m_current_value, m_value);
+
+ // The number of ALU cycles depends on the number of steps in
+ // the division algorithm. The number of cycles is between 32 and
+ // 48 (*2 for clock cycles)
+ // As I don't have a description of the actual algorithm, I'll use
+ // the following heuristic: We use 32 ALU cycles in general, then
+ // we need as many cycles as it takes to
+ // shift away the dividend. Thus, bigger dividends need more cycles.
+ pulse_clock(62); // one pulse is at the start, one at the end
+ value1 = m_register_contents & 0xffff;
+ while (value1 != 0)
+ {
+ value1 = (value1 >> 1) & 0xffff;
+ pulse_clock(2);
+ }
+ // We still have m_regnumber; this is where m_current_value will go to
+ break;
+ case 3:
+ // Prepare to write the remainder; we have the address from above
+ // (m_address), did not change
+ m_current_value = m_value;
+ if (VERBOSE>7) LOG("tms9900: ST = %04x (div)\n", ST);
+ break;
+ }
+ pulse_clock(2);
+ m_state++;
+}
+
+void tms9900_device::alu_xop()
+{
+ switch (m_state)
+ {
+ case 0:
+ // We have the effective address of the source operand in m_address
+ m_source_address = m_address;
+ // Now we take the XOP number from the instruction register
+ // and calculate the vector location
+ // [0010 11xx xx tt SSSS] shift 6 right, then *4 => shift 4 right
+ m_address = 0x0040 + ((IR >> 4) & 0x003c);
+ break;
+ case 1:
+ m_value = WP; // save the old WP
+ WP = m_current_value; // the new WP has been read in the previous microoperation
+ m_current_value = m_source_address; // we saved the address of the source operand; retrieve it
+ m_regnumber = 11; // Next register is R11
+ break;
+ case 2:
+ m_current_value = ST;
+ m_regnumber = 15;
+ break;
+ case 3:
+ m_current_value = PC;
+ m_regnumber = 14;
+ break;
+ case 4:
+ m_current_value = WP;
+ m_regnumber = 13;
+ break;
+ case 5:
+ m_address = 0x0042 + ((IR >> 4) & 0x003c); // location of new PC
+ ST |= ST_X;
+ break;
+ case 6:
+ PC = m_current_value;
+ break;
+ }
+ pulse_clock(2);
+ m_state++;
+}
+
+void tms9900_device::alu_clr_swpb()
+{
+ UINT32 dest_new = 0;
+ UINT32 src_val = m_current_value & 0x0000ffff;
+ UINT16 sign = 0;
+
+ bool setstatus = true;
+ bool check_ov = true;
+
+ switch (m_command)
+ {
+ case CLR:
+ // no status bits
+ m_current_value = 0x0000;
+ setstatus = false;
+ break;
+ case SETO:
+ // no status bits
+ m_current_value = 0xffff;
+ setstatus = false;
+ break;
+ case INV:
+ // LAE
+ dest_new = ~src_val & 0xffff;
+ check_ov = false;
+ break;
+ case NEG:
+ // LAEO
+ // Overflow occurs for value=0x8000
+ dest_new = ((~src_val) & 0x0000ffff) + 1;
+ check_ov = false;
+ set_status_bit(ST_OV, src_val == 0x8000);
+ break;
+ case INC:
+ // LAECO
+ // Overflow for result value = 0x8000
+ // Carry for result value = 0x0000
+ dest_new = src_val + 1;
+ break;
+ case INCT:
+ // LAECO
+ // Overflow for result value = 0x8000 / 0x8001
+ // Carry for result value = 0x0000 / 0x0001
+ dest_new = src_val + 2;
+ break;
+ case DEC:
+ // LAECO
+ // Carry for result value != 0xffff
+ // Overflow for result value == 0x7fff
+ dest_new = src_val + 0xffff;
+ sign = 0x8000;
+ break;
+ case DECT:
+ // Carry for result value != 0xffff / 0xfffe
+ // Overflow for result value = 0x7fff / 0x7ffe
+ dest_new = src_val + 0xfffe;
+ sign = 0x8000;
+ break;
+ case SWPB:
+ m_current_value = ((m_current_value << 8) | (m_current_value >> 8)) & 0xffff;
+ setstatus = false;
+ break;
+ }
+
+ if (setstatus)
+ {
+ if (check_ov) set_status_bit(ST_OV, ((src_val & 0x8000)!=sign) && ((dest_new & 0x8000)==sign));
+ set_status_bit(ST_C, (dest_new & 0x10000) != 0);
+ m_current_value = dest_new & 0xffff;
+ compare_and_set_lae(m_current_value, 0);
+ }
+
+ pulse_clock(2);
+ // No states here
+}
+
+void tms9900_device::alu_abs()
+{
+ // LAECO (from original word!)
+ // O if >8000
+ // C is alwas reset
+ set_status_bit(ST_OV, m_current_value == 0x8000);
+ set_status_bit(ST_C, false);
+ compare_and_set_lae(m_current_value, 0);
+
+ if ((m_current_value & 0x8000)!=0)
+ {
+ m_current_value = (-m_current_value) & 0xffff;
+ pulse_clock(2); // If ABS is performed it takes one machine cycle more
+ }
+ else
+ {
+ MPC++; // skips over the next micro operation (MEMORY_WRITE)
+ }
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_x()
+{
+ if (VERBOSE>7) LOG("tms9900: Substituting current command by %04x\n", m_current_value);
+ decode(m_current_value);
+ pulse_clock(2);
+}
+
+/*
+ Also used by other microprograms
+*/
+void tms9900_device::alu_b()
+{
+ // no status bits
+ // Although we got the contents of the source data, we do not use them
+ // but directly branch there. That is, we are only interested in the
+ // address of the source data.
+ // If we have a B *R5 and R5 contains the value 0xa000, the CPU actually
+ // retrieves the value at 0xa000, but in fact it will load the PC
+ // with the address 0xa000
+ PC = m_address;
+ if (VERBOSE>7) LOG("tms9900: Set new PC = %04x\n", PC);
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_bl()
+{
+ // no status bits
+ // We have just retrieved the target address; save it in another location
+ m_source_value = m_current_value;
+ m_current_value = PC;
+ m_regnumber = 11;
+ pulse_clock(4);
+ PC = m_address; // See B command: Forget about the value from the target location
+ if (VERBOSE>7) LOG("tms9900: Set new PC = %04x\n", PC);
+}
+
+void tms9900_device::alu_blwp()
+{
+ switch (m_state)
+ {
+ case 0:
+ m_source_address = m_address; // Save the location of the WP
+ m_value = WP;
+ WP = m_current_value; // set new WP (*m_destination)
+ m_current_value = ST; // get status register
+ m_regnumber = 15;
+ break;
+ case 1:
+ m_current_value = PC; // get program counter
+ m_regnumber = 14;
+ break;
+ case 2:
+ m_current_value = m_value; // retrieve the old WP
+ m_regnumber = 13;
+ break;
+ case 3:
+ m_address = (m_source_address + 2)&0xffff; // point to PC component of branch vector
+ break;
+ case 4:
+ PC = m_current_value;
+ break;
+ }
+ pulse_clock(2);
+ m_state++;
+}
+
+void tms9900_device::alu_ldcr()
+{
+ UINT16 value;
+
+ // Spec: "If the source operand address is odd, the address is truncated
+ // to an even address prior to data transfer."
+ // (Editor/Assembler, page 151)
+ // This refers to transfers with more than 8 bits. In this case, for
+ // LDCR the first bit is taken from the least significant bit of the
+ // source word. If the address is odd (e.g. 0x1001), it is
+ // treated as 0x1000, that is, truncated to an even address.
+ // For transfers with 1-8 bits, the first bit is the least significant
+ // bit of the source byte (any address).
+
+ if (m_state == 0)
+ {
+ m_regnumber = 12;
+ }
+ else
+ {
+ value = m_source_value; // copied by ALU_SOURCE
+ m_count = (IR >> 6) & 0x000f;
+ if (m_count == 0) m_count = 16;
+ if (m_count <= 8)
+ {
+ if (m_source_even) value>>=8;
+ set_status_parity((UINT8)(value & 0xff));
+ compare_and_set_lae(value<<8, 0);
+ }
+ else
+ {
+ compare_and_set_lae(value, 0);
+ }
+ m_cru_output = true;
+ m_cru_address = m_register_contents;
+ m_value = value;
+ if (VERBOSE>6) LOG("tms9900: Load CRU address %04x (%d bits), value = %04x\n", m_cru_address, m_count, m_value);
+ }
+ m_state++;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_stcr()
+{
+ UINT16 value;
+
+ // For STCR transfers with more than 8 bits, the first CRU bit is
+ // always put into the least significant bit of the destination word.
+ // If the address is odd (e.g. 0x1001), it is treated as 0x1000, that is,
+ // truncated to an even boundary.
+ // For transfers with 1-8 bits, the destination address is handled as
+ // in MOVB operations, i.e. the other byte of the word is kept unchanged.
+
+ switch (m_state)
+ {
+ case 0: // After getting the destination operand and saving the address/value
+ m_regnumber = 12;
+ break;
+ case 1: // After getting R12
+ m_cru_output = false;
+ m_cru_address = m_register_contents;
+ m_count = (IR >> 6) & 0x000f;
+ if (m_count == 0) m_count = 16;
+ break;
+ case 2: // After the cru operation; value starts at LSB of m_value
+ value = m_value & 0xffff;
+ if (m_count < 9)
+ {
+ if (VERBOSE>6) LOG("tms9900: Store CRU at %04x (%d bits) in %04x, result = %02x\n", m_cru_address, m_count, m_source_address, value);
+ set_status_parity((UINT8)(value & 0xff));
+ compare_and_set_lae(value<<8, 0);
+ if (m_source_even)
+ m_current_value = (m_source_value & 0x00ff) | (value<<8);
+ else
+ m_current_value = (m_source_value & 0xff00) | (value & 0xff);
+
+ pulse_clock(2*(5 + (8-m_count)));
+ }
+ else
+ {
+ if (VERBOSE>6) LOG("tms9900: Store CRU at %04x (%d bits) in %04x, result = %04x\n", m_cru_address, m_count, m_source_address, value);
+ m_current_value = value;
+ compare_and_set_lae(value, 0);
+ pulse_clock(2*(5 + (16-m_count)));
+ }
+ m_address = m_source_address;
+ break;
+ }
+
+ m_state++;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_sbz_sbo()
+{
+ INT8 displacement;
+ if (m_state==0)
+ {
+ m_regnumber = 12;
+ }
+ else
+ {
+ m_cru_output = true;
+ displacement = (IR & 0xff);
+ m_cru_address = m_register_contents + (displacement<<1);
+ m_count = 1;
+ m_value = (m_command == SBO)? 1 : 0;
+ pulse_clock(2);
+ }
+ m_state++;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_tb()
+{
+ INT8 displacement;
+ switch (m_state)
+ {
+ case 0:
+ m_regnumber = 12;
+ break;
+ case 1:
+ m_cru_output = false;
+ displacement = (IR & 0xff);
+ m_cru_address = m_register_contents + (displacement<<1);
+ m_count = 1;
+ break;
+ case 2:
+ set_status_bit(ST_EQ, m_value!=0);
+ if (VERBOSE>7) LOG("tms9900: ST = %04x\n", ST);
+ break;
+ }
+ m_state++;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_jmp()
+{
+ INT8 displacement;
+ bool cond = false;
+
+ if (m_state==0)
+ {
+ switch (m_command)
+ {
+ case JMP:
+ cond = true;
+ break;
+ case JLT: // LAECOP == x00xxx
+ cond = ((ST & (ST_AGT | ST_EQ))==0);
+ break;
+ case JLE: // LAECOP == 0xxxxx
+ cond = ((ST & ST_LH)==0);
+ break;
+ case JEQ: // LAECOP == xx1xxx
+ cond = ((ST & ST_EQ)!=0);
+ break;
+ case JHE: // LAECOP == 1x0xxx, 0x1xxx
+ cond = ((ST & (ST_LH | ST_EQ)) != 0);
+ break;
+ case JGT: // LAECOP == x1xxxx
+ cond = ((ST & ST_AGT)!=0);
+ break;
+ case JNE: // LAECOP == xx0xxx
+ cond = ((ST & ST_EQ)==0);
+ break;
+ case JNC: // LAECOP == xxx0xx
+ cond = ((ST & ST_C)==0);
+ break;
+ case JOC: // LAECOP == xxx1xx
+ cond = ((ST & ST_C)!=0);
+ break;
+ case JNO: // LAECOP == xxxx0x
+ cond = ((ST & ST_OV)==0);
+ break;
+ case JL: // LAECOP == 0x0xxx
+ cond = ((ST & (ST_LH | ST_EQ)) == 0);
+ break;
+ case JH: // LAECOP == 1xxxxx
+ cond = ((ST & ST_LH)!=0);
+ break;
+ case JOP: // LAECOP == xxxxx1
+ cond = ((ST & ST_OP)!=0);
+ break;
+ }
+ pulse_clock(2);
+ if (!cond)
+ {
+ if (VERBOSE>7) LOG("tms9900: Jump condition false\n");
+ MPC+=1; // skip next ALU call
+ }
+ else
+ if (VERBOSE>7) LOG("tms9900: Jump condition true\n");
+ }
+ else
+ {
+ displacement = (IR & 0xff);
+ PC = PC + (displacement<<1);
+ pulse_clock(2);
+ }
+ m_state++;
+}
+
+void tms9900_device::alu_shift()
+{
+ bool carry = false;
+ bool overflow = false;
+ UINT16 sign = 0;
+ UINT32 value;
+
+ switch (m_state)
+ {
+ case 0:
+ m_regnumber = (IR & 0x000f);
+ break;
+ case 1:
+ m_source_value = m_current_value; // save it
+ m_count = (IR >> 4) & 0x000f;
+ if (m_count != 0) MPC++;
+ else m_regnumber = 0;
+ break;
+ case 2:
+ value = m_source_value & 0xffff;
+
+ if (m_count==0) m_count = m_register_contents & 0x000f;
+ if (m_count==0) m_count = 16;
+
+ // we are re-implementing the shift operations because we have to pulse
+ // the clock at each single shift anyway.
+ // Also, it is easier to implement the status bit setting.
+ // Note that count is never 0
+ if (m_command == SRA) sign = value & 0x8000;
+
+ for (int i=0; i < m_count; i++)
+ {
+ switch (m_command)
+ {
+ case SRL:
+ case SRA:
+ carry = ((value & 1)!=0);
+ value = (value >> 1) | sign;
+ break;
+ case SLA:
+ carry = ((value & 0x8000)!=0);
+ value <<= 1;
+ if (carry != ((value&0x8000)!=0)) overflow = true;
+ break;
+ case SRC:
+ carry = ((value & 1)!=0);
+ value = (value>>1) | (carry? 0x8000 : 0x0000);
+ break;
+ }
+ pulse_clock(2);
+ }
+
+ m_current_value = value & 0xffff;
+ set_status_bit(ST_C, carry);
+ set_status_bit(ST_OV, overflow);
+ compare_and_set_lae(m_current_value, 0);
+
+ m_regnumber = (IR & 0x000f);
+ break;
+ }
+ m_state++;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_ai_ori()
+{
+ UINT32 dest_new = 0;
+ switch (m_command)
+ {
+ case AI:
+ dest_new = m_current_value + m_register_contents;
+ // See status bit handling for Add
+ set_status_bit(ST_C, (dest_new & 0x10000) != 0);
+ set_status_bit(ST_OV, ((dest_new ^ m_current_value) & (dest_new ^ m_register_contents) & 0x8000)!=0);
+ break;
+ case ANDI:
+ dest_new = m_current_value & m_register_contents;
+ break;
+ case ORI:
+ dest_new = m_current_value | m_register_contents;
+ break;
+ }
+ m_current_value = dest_new & 0xffff;
+ compare_and_set_lae(m_current_value, 0);
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_ci()
+{
+ compare_and_set_lae(m_register_contents, m_current_value);
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_li()
+{
+ compare_and_set_lae(m_current_value, 0);
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_lwpi()
+{
+ WP = m_current_value;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_limi()
+{
+ ST = (ST & 0xfff0) | (m_current_value & 0x000f);
+ if (VERBOSE>7) LOG("tms9900: ST = %04x\n", ST);
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_stwp_stst()
+{
+ if (m_command==STST) m_current_value = ST;
+ else m_current_value = WP;
+ pulse_clock(2);
+}
+
+void tms9900_device::alu_external()
+{
+ pulse_clock(10);
+
+ // Call some possibly attached external device
+ // We pass the bit pattern of the address bus to the external function
+
+ // IDLE = 0000 0011 0100 0000
+ // RSET = 0000 0011 0110 0000
+ // CKON = 0000 0011 1010 0000
+ // CKOF = 0000 0011 1100 0000
+ // LREX = 0000 0011 1110 0000
+ // ---
+ if (m_command == IDLE)
+ m_idle_state = true;
+
+ m_external_operation((IR >> 5) & 0x07, 1);
+}
+
+void tms9900_device::alu_rtwp()
+{
+ switch (m_state)
+ {
+ case 0:
+ m_regnumber = 15;
+ break;
+ case 1:
+ ST = m_register_contents;
+ m_regnumber = 14;
+ break;
+ case 2:
+ PC = m_register_contents;
+ m_regnumber = 13;
+ break;
+ case 3:
+ WP = m_register_contents;
+ break;
+ }
+ m_state++;
+ pulse_clock(2);
+}
+
+
+void tms9900_device::alu_int()
+{
+ if (VERBOSE>7) LOG("tms9900: INT state %d; irq_level %d\n", m_state, m_irq_level);
+ switch (m_state)
+ {
+ case 0:
+ if (m_irq_level == RESET_INT)
+ {
+ m_address = 0;
+ pulse_clock(2);
+ }
+ else
+ {
+ if (m_irq_level == LOAD_INT) m_address = 0xfffc;
+ else
+ {
+ m_address = (m_irq_level << 2);
+ }
+ }
+ break;
+ case 1:
+ m_value = WP; // old WP
+ WP = m_current_value; // new WP
+ m_current_value = ST;
+ m_regnumber = 15;
+ break;
+ case 2:
+ m_current_value = PC;
+ m_regnumber = 14;
+ break;
+ case 3:
+ m_current_value = m_value; // old WP
+ m_regnumber = 13;
+ break;
+ case 4:
+ m_address = (m_address + 2) & 0xfffe;
+ if (VERBOSE>7) LOG("tms9900: read from %04x\n", m_address);
+ break;
+ case 5:
+ PC = m_current_value;
+ if (m_irq_level > 0 )
+ {
+ ST = (ST & 0xfff0) | (m_irq_level - 1);
+ }
+ break;
+ }
+ m_state++;
+ pulse_clock(2);
+}
+
+/**************************************************************************/
+UINT32 tms9900_device::execute_min_cycles() const
+{
+ return 2;
+}
+
+// TODO: Compute this value, just a wild guess for the average
+UINT32 tms9900_device::execute_max_cycles() const
+{
+ return 10;
+}
+
+UINT32 tms9900_device::execute_input_lines() const
+{
+ return 1;
+}
+
+// clocks to cycles, cycles to clocks = id
+// execute_default_irq_vector = 0
+// execute_burn = nop
+
+// device_disasm_interface overrides
+UINT32 tms9900_device::disasm_min_opcode_bytes() const
+{
+ return 2;
+}
+
+UINT32 tms9900_device::disasm_max_opcode_bytes() const
+{
+ return 6;
+}
+
+offs_t tms9900_device::disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options)
+{
+ return Dasm9900(buffer, pc, TMS9900_ID, oprom, opram);
+}
-#include "99xxcore.h"
-DEFINE_LEGACY_CPU_DEVICE(TMS9900, tms9900);
+const device_type TMS9900 = &device_creator<tms9900_device>;
diff --git a/src/emu/cpu/tms9900/tms9900.h b/src/emu/cpu/tms9900/tms9900.h
index c6decc93378..12904b44ad0 100644
--- a/src/emu/cpu/tms9900/tms9900.h
+++ b/src/emu/cpu/tms9900/tms9900.h
@@ -1,210 +1,389 @@
/*
- tms9900.h
+ TMS9900 processor
+ This is a re-implementation of the TMS9900 featuring a cycle-precise
+ behaviour.
- C Header file for TMS9900 core
-*/
+ See tms9900.c for documentation
-#pragma once
+ Types of TMS99xx processors:
+ TI990/9 Early implementation, used in a few real-world applications, 1974
+ very similar to mapper-less 990/10 and tms9900, but the Load
+ process is different
-#ifndef __TMS9900_H__
-#define __TMS9900_H__
+ TI990/10 Original multi-chip implementation for minicomputer systems, 1975
+ TI990/12 Multi-chip implementation, faster than 990/10. Huge instruction set
-/*#define TI990_9_ID 0*//* early implementation, used in a few real-world applications, 1974 */
- /* very similar to mapper-less 990/10 and tms9900, but the Load process */
- /* is different */
- /* ("ti990/9" is likely to be a nickname) */
-#define TI990_10_ID 1 /* original multi-chip implementation for minicomputer systems, 1975 */
-/*#define TI990_12_ID 2*//* multi-chip implementation, faster than 990/10. huge instruction set */
- /* (144 instructions, with up to 16 additional custom instructions simulteanously) */
- /* 1979 (or before) */
-#define TMS9900_ID 3 /* mono-chip implementation, 1976 */
-#define TMS9940_ID 4 /* microcontroller with 2kb ROM, 128b RAM, decrementer, CRU bus, 1979 */
-#define TMS9980_ID 5 /* 8-bit variant of tms9900. Two distinct chips actually : tms9980a, */
- /* and tms9981 with an extra clock and simplified power supply */
-#define TMS9985_ID 6 /* 9940 with 8kb ROM, 256b RAM, and a 8-bit external bus, c. 1978 (never released) */
-#define TMS9989_ID 7 /* improved 9980, used in bombs, missiles, and other *nice* hardware */
-/*#define SBP68689_ID 8*//* improved 9989, built as an ASIC as 9989 was running scarce */
-#define TMS9995_ID 9 /* tms9985-like, with many improvements (but no ROM) */
-#define TMS99000_ID 10/* improved mono-chip implementation, meant to replace 990/10, 1981 */
- /* This chip is available in several variants (tms99105, tms99110...), */
- /* which are similar but emulate additional instructions thanks */
- /* to the so-called macrostore feature. */
-#define TMS99105A_ID 11
-#define TMS99110A_ID 12
-
-
-/* NPW 25-May-2002 - Added these to get it to compile under windows */
-#define TI9940_ID TMS9940_ID
-#define TI9985_ID TMS9985_ID
+ TMS9900 Mono-chip implementation, 1976. Used in the TI-99/4(A) computer.
+ TMS9940 Microcontroller with 2kb ROM, 128b RAM, decrementer, CRU bus, 1979
-enum
-{
- TMS9900_PC=1, TMS9900_WP, TMS9900_STATUS, TMS9900_IR,
- TMS9900_R0, TMS9900_R1, TMS9900_R2, TMS9900_R3,
- TMS9900_R4, TMS9900_R5, TMS9900_R6, TMS9900_R7,
- TMS9900_R8, TMS9900_R9, TMS9900_R10, TMS9900_R11,
- TMS9900_R12, TMS9900_R13, TMS9900_R14, TMS9900_R15
-};
+ TMS9980 8-bit variant of tms9900. Two distinct chips actually : tms9980a
+ and tms9981 with an extra clock and simplified power supply
-typedef void (*ti99xx_idle_func)(device_t *device, int state);
-typedef void (*ti99xx_rset_func)(device_t *device);
-typedef void (*ti99xx_lrex_func)(device_t *device);
-typedef void (*ti99xx_ckon_ckof_func)(device_t *device, int state);
-typedef void (*ti99xx_error_interrupt_func)(device_t *device, int state);
+ TMS9985 9940 with 8kb ROM, 256b RAM, and a 8-bit external bus, c. 1978 (never released)
+ TMS9989 Improved 9980, used in military hardware.
-DECLARE_LEGACY_CPU_DEVICE(TI990_10, ti990_10);
+ SBP68689 Improved 9989, built as an ASIC as 9989 was running scarce
-/*
- structure with the parameters ti990_10_reset wants.
-*/
+ TMS9995 TMS9985-like, with many improvements (but no ROM). Used in the
+ TI-99/8 prototype and the Geneve computer.
-typedef struct ti990_10reset_param
-{
- ti99xx_idle_func idle_callback;
- ti99xx_rset_func rset_callback;
- ti99xx_lrex_func lrex_callback;
- ti99xx_ckon_ckof_func ckon_ckof_callback;
- ti99xx_error_interrupt_func error_interrupt_callback;
-} ti990_10reset_param;
+ TMS99000 Improved mono-chip implementation, meant to replace 990/10, 1981
+ TMS99105 This chip is available in several variants which are similar
+ TMS99110 but emulate additional instructions, thanks to the so-called
+ macrostore feature.
-/* accessor for the internal ROM */
-extern READ16_HANDLER(ti990_10_internal_r);
-
-/* CRU accessor for the mapper registers (R12 base 0x1fa0) */
-extern READ8_HANDLER(ti990_10_mapper_cru_r);
-extern WRITE8_HANDLER(ti990_10_mapper_cru_w);
-/* CRU accessor for the error interrupt register (R12 base 0x1fc0) */
-extern READ8_HANDLER(ti990_10_eir_cru_r);
-extern WRITE8_HANDLER(ti990_10_eir_cru_w);
-
-
-
-DECLARE_LEGACY_CPU_DEVICE(TMS9900, tms9900);
-
-/*
- structure with optional parameters for tms9900_reset.
+ In this implementation we only consider TMS9900, 9980, and 9995. The
+ remaining types are implemented on an own code base as they introduce
+ significant changes (e.g. privileged mode, address mapper). For now we
+ leave the implementation of the rest up to 99xxcore.h.
*/
-typedef struct tms9900reset_param
-{
- ti99xx_idle_func idle_callback;
-} tms9900reset_param;
-
-
-
-//DECLARE_LEGACY_CPU_DEVICE(TMS9940, tms9940);
-
-/*
- structure with optional parameters for tms9940_reset.
-*/
-typedef struct tms9940reset_param
-{
- ti99xx_idle_func idle_callback;
-} tms9940reset_param;
-
-
-
-DECLARE_LEGACY_CPU_DEVICE(TMS9980, tms9980a);
-
-/*
- structure with optional parameters for tms9980a_reset.
-*/
-typedef struct tms9980areset_param
-{
- ti99xx_idle_func idle_callback;
-} tms9980areset_param;
-
+#ifndef __TMS9900_H__
+#define __TMS9900_H__
-//DECLARE_LEGACY_CPU_DEVICE(TMS9985, tms9985);
+#include "emu.h"
+#include "debugger.h"
-/*//
- structure with optional parameters for tms9985_reset.
-*/
-typedef struct tms9985reset_param
+enum
{
- ti99xx_idle_func idle_callback;
-} tms9985reset_param;
-
-
+ TI990_10_ID = 1,
+ TMS9900_ID = 3,
+ TMS9940_ID = 4,
+ TMS9980_ID = 5,
+ TMS9985_ID = 6,
+ TMS9989_ID = 7,
+ TMS9995_ID = 9,
+ TMS99000_ID = 10,
+ TMS99105A_ID = 11,
+ TMS99110A_ID = 12
+};
-//DECLARE_LEGACY_CPU_DEVICE(TMS9989, tms9989);
+#define MCFG_TMS9900_ADD(_tag, _device, _clock, _prgmap, _iomap, _config) \
+ MCFG_DEVICE_ADD(_tag, _device, _clock) \
+ MCFG_DEVICE_PROGRAM_MAP(_prgmap) \
+ MCFG_DEVICE_IO_MAP(_iomap) \
+ MCFG_DEVICE_CONFIG(_config)
-/*
- structure with optional parameters for tms9989_reset.
-*/
-typedef struct tms9989reset_param
+enum
{
- ti99xx_idle_func idle_callback;
-} tms9989reset_param;
-
-
-
-DECLARE_LEGACY_CPU_DEVICE(TMS9995, tms9995);
+ IDLE_OP = 2,
+ RSET_OP = 3,
+ CKOF_OP = 5,
+ CKON_OP = 6,
+ LREX_OP = 7
+};
-/*
- structure with the parameters tms9995_reset wants.
-*/
-typedef struct tms9995reset_param
+typedef struct _tms9900_config
{
- /* auto_wait_state : a non-zero value makes tms9995 generate a wait state automatically on each
- memory access */
- int auto_wait_state;
-
- ti99xx_idle_func idle_callback;
-
- /* on the tms9995-mp9537, internal RAM and decrementer register are
- disabled. This chip is used by the ti99/8 so that internal RAM does
- not prevent the mapper from working correctly. */
- int is_mp9537;
-} tms9995reset_param;
-
-/* accessor for the first 252 bytes of internal RAM */
-extern READ8_HANDLER(tms9995_internal1_r);
-extern WRITE8_HANDLER(tms9995_internal1_w);
-/* accessors for the last 4 bytes of internal RAM */
-extern READ8_HANDLER(tms9995_internal2_r);
-extern WRITE8_HANDLER(tms9995_internal2_w);
-
-
-
-//DECLARE_LEGACY_CPU_DEVICE(TMS99000, tms99000);
-
-/*
- structure with optional parameters for tms99000_reset.
-*/
-typedef struct tms99000reset_param
+ devcb_write8 external_callback;
+ devcb_read8 irq_level;
+ devcb_write_line instruction_acquisition;
+ devcb_write_line clock_out;
+ devcb_write_line wait_line;
+ devcb_write_line holda_line;
+} tms9900_config;
+
+#define TMS9900_CONFIG(name) \
+ const tms9900_config(name) =
+
+class tms9900_device : public cpu_device
{
- ti99xx_idle_func idle_callback;
-} tms99000reset_param;
-
-
+public:
+ tms9900_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock);
+
+ // READY input line. When asserted (high), the memory is ready for data exchange.
+ void set_ready(int state);
+
+ // HOLD input line. When asserted (low), the CPU is requested to release the
+ // data and address bus and enter the HOLD state. The entrance of this state
+ // is acknowledged by the HOLDA output line.
+ void set_hold(int state);
+
+protected:
+ // device-level overrides
+ virtual void device_start();
+ virtual void device_stop();
+ virtual void device_reset();
+
+ // device_execute_interface overrides
+ virtual UINT32 execute_min_cycles() const;
+ virtual UINT32 execute_max_cycles() const;
+ virtual UINT32 execute_input_lines() const;
+ virtual void execute_set_input(int irqline, int state);
+ virtual void execute_run();
+
+ // device_disasm_interface overrides
+ virtual UINT32 disasm_min_opcode_bytes() const;
+ virtual UINT32 disasm_max_opcode_bytes() const;
+ virtual offs_t disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options);
+
+ const address_space_config* memory_space_config(address_spacenum spacenum) const;
+
+private:
+ // TMS9900 hardware registers
+ UINT16 WP; // Workspace pointer
+ UINT16 PC; // Program counter
+ UINT16 ST; // Status register
+
+ // Internal register
+ UINT16 IR; // Instruction register
+
+ // Decoded command
+ UINT16 m_command;
+
+ // Indicates if this is a byte-oriented command
+ inline bool byte_operation();
+
+ const address_space_config m_program_config;
+ const address_space_config m_io_config;
+ address_space* m_prgspace;
+ address_space* m_cru;
+
+ // Processor states
+ bool m_idle_state;
+ bool m_load_state;
+ bool m_irq_state;
+ bool m_ready_state;
+ bool m_wait_state;
+ bool m_hold_state;
+
+ bool m_reset;
+
+ int m_irq_level; // Interrupt level as acquired from input lines IC0-IC3
+ int m_icount; // Cycle counter
+
+ // State / debug management
+ UINT16 m_state_any;
+ static const char* s_statename[];
+ void state_import(const device_state_entry &entry);
+ void state_export(const device_state_entry &entry);
+ void state_string_export(const device_state_entry &entry, astring &string);
+ UINT16 read_workspace_register_debug(int reg);
+ void write_workspace_register_debug(int reg, UINT16 data);
+
+ // Interrupt handling
+ void service_interrupt();
+
+ // ================ Microprogram support ========================
+
+ // Set up lookup table
+ void build_command_lookup_table();
+
+ // Sequence of micro-operations
+ typedef const UINT8* microprogram;
+
+ // Method pointer
+ typedef void (tms9900_device::*ophandler)(void);
+
+ // Opcode list entry
+ typedef struct _tms_instruction
+ {
+ UINT16 opcode;
+ int id;
+ int format;
+ microprogram prog; // Microprogram
+ } tms_instruction;
+
+ // Lookup table entry
+ typedef struct _lookup_entry
+ {
+ struct _lookup_entry *next_digit;
+ const tms_instruction *entry;
+ } lookup_entry;
+
+ // Pointer to the lookup table
+ lookup_entry* m_command_lookup_table;
+
+ // List of allocated tables (used for easy clean-up on exit)
+ lookup_entry* m_lotables[32];
+
+ // List of pointers for micro-operations
+ static const tms9900_device::ophandler s_microoperation[];
+
+ // Opcode table
+ static const tms9900_device::tms_instruction s_command[];
+
+ // Micro-operation declarations
+ void acquire_instruction(void);
+ void mem_read(void);
+ void mem_write(void);
+ void register_read(void);
+ void register_write(void);
+ void cru_operation(void);
+ void data_derivation_subprogram(void);
+ void return_from_subprogram(void);
+ void command_completed(void);
+
+ void alu_nop(void);
+ void alu_clear(void);
+ void alu_source(void);
+ void alu_setaddr(void);
+ void alu_addone(void);
+ void alu_setaddr_addone(void);
+ void alu_pcaddr_advance(void);
+ void alu_add_register(void);
+
+ void alu_imm(void);
+ void alu_reg(void);
+
+ void alu_f1(void);
+ void alu_comp(void);
+ void alu_f3(void);
+ void alu_multiply(void);
+ void alu_divide(void);
+ void alu_xop(void);
+ void alu_clr_swpb(void);
+ void alu_abs(void);
+ void alu_x(void);
+ void alu_b(void);
+ void alu_bl(void);
+ void alu_blwp(void);
+ void alu_ldcr(void);
+ void alu_stcr(void);
+ void alu_sbz_sbo(void);
+ void alu_tb(void);
+ void alu_jmp(void);
+ void alu_shift(void);
+ void alu_ai_ori(void);
+ void alu_ci(void);
+ void alu_li(void);
+ void alu_lwpi(void);
+ void alu_limi(void);
+ void alu_stwp_stst(void);
+ void alu_external(void);
+ void alu_rtwp(void);
+ void alu_int(void);
+
+ void abort_operation(void);
+ UINT16 pulse_and_read_memory(UINT16 address);
+ void pulse_and_write_memory(UINT16 address, UINT16 data);
+ void decode(UINT16 inst);
+
+ // Micro-operation
+ UINT8 m_op;
+
+ // Micro-operation program counter (as opposed to the program counter PC)
+ int MPC;
+
+ // Current microprogram
+ const UINT8* m_program;
+
+ // Calling microprogram (used when data derivation is called)
+ const UINT8* m_caller;
+ int m_caller_MPC;
+
+ // State of the micro-operation. Needed for repeated ALU calls.
+ int m_state;
+
+ // Check the READY line?
+ bool m_check_ready;
+
+ // Has HOLD been acknowledged yet?
+ bool m_hold_acknowledged;
+
+ // Issue clock pulses. Note that each machine cycle has two clock cycles.
+ inline void pulse_clock(int count);
+
+ // Signal the wait state via the external line
+ inline void set_wait_state(bool state);
+
+ // Used to acknowledge HOLD and enter the HOLD state
+ inline void acknowledge_hold();
+
+ // Stored address
+ UINT16 m_address;
+
+ // Stores the recently read word or the word to be written
+ UINT16 m_current_value;
+
+ // Was the source operand a byte from an even address?
+ bool m_source_even;
+
+ // Was the destination operand a byte from an even address?
+ bool m_destination_even;
+
+ // Intermediate storage for the source operand
+ UINT16 m_source_address;
+ UINT16 m_source_value;
+
+ // Stores the recently read register contents
+ UINT16 m_register_contents;
+
+ // Stores the register number for the next register access
+ int m_regnumber;
+
+ // CRU support: Indicates whether the CRU shall be configured to output mode
+ bool m_cru_output;
+
+ // CRU support: Stores the CRU address
+ UINT16 m_cru_address;
+
+ // CRU support: Stores the number of bits to be transferred
+ int m_count;
+
+ // Another internal register, storing intermediate values
+ // Using 32 bits to support MPY
+ UINT32 m_value;
+
+ // For two-argument commands. Indicates whether this is the second operand.
+ bool m_get_destination;
+
+ // Status register update
+ inline void set_status_bit(int bit, bool state);
+ inline void compare_and_set_lae(UINT16 value1, UINT16 value2);
+ void set_status_parity(UINT8 value);
+
+ // Used to display the number of consumed cycles in the log.
+ int m_first_cycle;
+
+ /************************************************************************/
+
+ // Trigger external operation. This is achieved by putting a special value in
+ // the most significant three bits of the address bus (TMS9995: data bus) and
+ // pulsing the CRUCLK line.
+ // Accordingly, we have
+ //
+ // A0 A1 A2 A3 A4 A5 ... A12 A13 A14 A15
+ // 0 0 0 x x x x x x - normal CRU access
+ // 0 1 0 x x x x x x - IDLE
+ // 0 1 1 x x x x x x - RSET
+ // 1 0 1 x x x x x x - CKON
+ // 1 1 0 x x x x x x - CKOF
+ // 1 1 1 x x x x x x - LREX
+ //
+ // so the TMS9900 can only use CRU addresses 0 - 1ffe for CRU operations.
+ // By moving these three bits to the data bus, the TMS9995 can allow for the
+ // full range 0000-fffe for its CRU operations.
+ //
+ // We could realize this via the CRU access as well, but the data bus access
+ // is not that simple to emulate. For the sake of homogenity between the
+ // chip emulations we use a dedicated callback.
+ devcb_resolved_write8 m_external_operation;
-//DECLARE_LEGACY_CPU_DEVICE(TMS99105A, tms99105a);
-
-/*
- structure with optional parameters for tms99105a_reset.
-*/
-typedef struct tms99105areset_param
-{
- ti99xx_idle_func idle_callback;
-} tms99105areset_param;
+ // Get the value of the interrupt level lines
+ devcb_resolved_read8 m_get_ic0123;
+ // Signal to the outside world that we are now getting an instruction
+ devcb_resolved_write_line m_iaq_line;
+ // Clock output. This is not a pin of the TMS9900 because the TMS9900
+ // needs an external clock, and usually one of those external lines is
+ // used for this purpose.
+ devcb_resolved_write_line m_clock_out_line;
-//DECLARE_LEGACY_CPU_DEVICE(TMS99110A, tms99110a);
+ // Wait output. When asserted (high), the CPU is in a wait state.
+ devcb_resolved_write_line m_wait_line;
-/*
- structure with optional parameters for tms99110a_reset.
-*/
-typedef struct tms99110areset_param
-{
- ti99xx_idle_func idle_callback;
-} tms99110areset_param;
+ // HOLD Acknowledge line. When asserted (high), the CPU is in HOLD state.
+ devcb_resolved_write_line m_holda_line;
+};
+unsigned Dasm9900(char *buffer, unsigned pc, int model_id, const UINT8 *oprom, const UINT8 *opram);
-unsigned Dasm9900 (char *buffer, unsigned pc, int model_id, const UINT8 *oprom, const UINT8 *opram);
+// device type definition
+extern const device_type TMS9900;
#endif /* __TMS9900_H__ */
diff --git a/src/emu/cpu/tms9900/tms9900l.c b/src/emu/cpu/tms9900/tms9900l.c
new file mode 100644
index 00000000000..9a75b735a53
--- /dev/null
+++ b/src/emu/cpu/tms9900/tms9900l.c
@@ -0,0 +1,15 @@
+/*
+ This is the previous implementation of the TMS9900 using the common
+ core implementation in 99xxcore. The new cycle-precise implementation
+ can be found in tms9900.c
+*/
+
+#include "emu.h"
+#include "debugger.h"
+#include "tms9900l.h"
+
+#define TMS99XX_MODEL TMS9900_ID
+
+#include "99xxcore.h"
+
+DEFINE_LEGACY_CPU_DEVICE(TMS9900L, tms9900l);
diff --git a/src/emu/cpu/tms9900/tms9900l.h b/src/emu/cpu/tms9900/tms9900l.h
new file mode 100644
index 00000000000..e2e778d96d7
--- /dev/null
+++ b/src/emu/cpu/tms9900/tms9900l.h
@@ -0,0 +1,210 @@
+/*
+ tms9900.h
+
+ C Header file for TMS9900 core
+*/
+
+#pragma once
+
+#ifndef __TMS9900_H__
+#define __TMS9900_H__
+
+
+/*#define TI990_9_ID 0*//* early implementation, used in a few real-world applications, 1974 */
+ /* very similar to mapper-less 990/10 and tms9900, but the Load process */
+ /* is different */
+ /* ("ti990/9" is likely to be a nickname) */
+#define TI990_10_ID 1 /* original multi-chip implementation for minicomputer systems, 1975 */
+/*#define TI990_12_ID 2*//* multi-chip implementation, faster than 990/10. huge instruction set */
+ /* (144 instructions, with up to 16 additional custom instructions simulteanously) */
+ /* 1979 (or before) */
+#define TMS9900_ID 3 /* mono-chip implementation, 1976 */
+#define TMS9940_ID 4 /* microcontroller with 2kb ROM, 128b RAM, decrementer, CRU bus, 1979 */
+#define TMS9980_ID 5 /* 8-bit variant of tms9900. Two distinct chips actually : tms9980a, */
+ /* and tms9981 with an extra clock and simplified power supply */
+#define TMS9985_ID 6 /* 9940 with 8kb ROM, 256b RAM, and a 8-bit external bus, c. 1978 (never released) */
+#define TMS9989_ID 7 /* improved 9980, used in bombs, missiles, and other *nice* hardware */
+/*#define SBP68689_ID 8*//* improved 9989, built as an ASIC as 9989 was running scarce */
+#define TMS9995_ID 9 /* tms9985-like, with many improvements (but no ROM) */
+#define TMS99000_ID 10/* improved mono-chip implementation, meant to replace 990/10, 1981 */
+ /* This chip is available in several variants (tms99105, tms99110...), */
+ /* which are similar but emulate additional instructions thanks */
+ /* to the so-called macrostore feature. */
+#define TMS99105A_ID 11
+#define TMS99110A_ID 12
+
+
+/* NPW 25-May-2002 - Added these to get it to compile under windows */
+#define TI9940_ID TMS9940_ID
+#define TI9985_ID TMS9985_ID
+
+
+enum
+{
+ TMS9900_PC=1, TMS9900_WP, TMS9900_STATUS, TMS9900_IR,
+ TMS9900_R0, TMS9900_R1, TMS9900_R2, TMS9900_R3,
+ TMS9900_R4, TMS9900_R5, TMS9900_R6, TMS9900_R7,
+ TMS9900_R8, TMS9900_R9, TMS9900_R10, TMS9900_R11,
+ TMS9900_R12, TMS9900_R13, TMS9900_R14, TMS9900_R15
+};
+
+typedef void (*ti99xx_idle_func)(device_t *device, int state);
+typedef void (*ti99xx_rset_func)(device_t *device);
+typedef void (*ti99xx_lrex_func)(device_t *device);
+typedef void (*ti99xx_ckon_ckof_func)(device_t *device, int state);
+typedef void (*ti99xx_error_interrupt_func)(device_t *device, int state);
+
+
+DECLARE_LEGACY_CPU_DEVICE(TI990_10L, ti990_10l);
+
+/*
+ structure with the parameters ti990_10_reset wants.
+*/
+
+typedef struct ti990_10reset_param
+{
+ ti99xx_idle_func idle_callback;
+ ti99xx_rset_func rset_callback;
+ ti99xx_lrex_func lrex_callback;
+ ti99xx_ckon_ckof_func ckon_ckof_callback;
+ ti99xx_error_interrupt_func error_interrupt_callback;
+} ti990_10reset_param;
+
+/* accessor for the internal ROM */
+extern READ16_HANDLER(ti990_10_internal_r);
+
+/* CRU accessor for the mapper registers (R12 base 0x1fa0) */
+extern READ8_HANDLER(ti990_10_mapper_cru_r);
+extern WRITE8_HANDLER(ti990_10_mapper_cru_w);
+/* CRU accessor for the error interrupt register (R12 base 0x1fc0) */
+extern READ8_HANDLER(ti990_10_eir_cru_r);
+extern WRITE8_HANDLER(ti990_10_eir_cru_w);
+
+
+
+DECLARE_LEGACY_CPU_DEVICE(TMS9900L, tms9900l);
+
+/*
+ structure with optional parameters for tms9900_reset.
+*/
+typedef struct tms9900reset_param
+{
+ ti99xx_idle_func idle_callback;
+} tms9900reset_param;
+
+
+
+//DECLARE_LEGACY_CPU_DEVICE(TMS9940, tms9940);
+
+/*
+ structure with optional parameters for tms9940_reset.
+*/
+typedef struct tms9940reset_param
+{
+ ti99xx_idle_func idle_callback;
+} tms9940reset_param;
+
+
+
+DECLARE_LEGACY_CPU_DEVICE(TMS9980L, tms9980al);
+
+/*
+ structure with optional parameters for tms9980a_reset.
+*/
+typedef struct tms9980areset_param
+{
+ ti99xx_idle_func idle_callback;
+} tms9980areset_param;
+
+
+
+//DECLARE_LEGACY_CPU_DEVICE(TMS9985, tms9985);
+
+/*//
+ structure with optional parameters for tms9985_reset.
+*/
+typedef struct tms9985reset_param
+{
+ ti99xx_idle_func idle_callback;
+} tms9985reset_param;
+
+
+
+//DECLARE_LEGACY_CPU_DEVICE(TMS9989, tms9989);
+
+/*
+ structure with optional parameters for tms9989_reset.
+*/
+typedef struct tms9989reset_param
+{
+ ti99xx_idle_func idle_callback;
+} tms9989reset_param;
+
+
+
+DECLARE_LEGACY_CPU_DEVICE(TMS9995L, tms9995l);
+
+/*
+ structure with the parameters tms9995_reset wants.
+*/
+typedef struct tms9995reset_param
+{
+ /* auto_wait_state : a non-zero value makes tms9995 generate a wait state automatically on each
+ memory access */
+ int auto_wait_state;
+
+ ti99xx_idle_func idle_callback;
+
+ /* on the tms9995-mp9537, internal RAM and decrementer register are
+ disabled. This chip is used by the ti99/8 so that internal RAM does
+ not prevent the mapper from working correctly. */
+ int is_mp9537;
+} tms9995reset_param;
+
+/* accessor for the first 252 bytes of internal RAM */
+extern READ8_HANDLER(tms9995_internal1_r);
+extern WRITE8_HANDLER(tms9995_internal1_w);
+/* accessors for the last 4 bytes of internal RAM */
+extern READ8_HANDLER(tms9995_internal2_r);
+extern WRITE8_HANDLER(tms9995_internal2_w);
+
+
+
+//DECLARE_LEGACY_CPU_DEVICE(TMS99000, tms99000);
+
+/*
+ structure with optional parameters for tms99000_reset.
+*/
+typedef struct tms99000reset_param
+{
+ ti99xx_idle_func idle_callback;
+} tms99000reset_param;
+
+
+
+//DECLARE_LEGACY_CPU_DEVICE(TMS99105A, tms99105a);
+
+/*
+ structure with optional parameters for tms99105a_reset.
+*/
+typedef struct tms99105areset_param
+{
+ ti99xx_idle_func idle_callback;
+} tms99105areset_param;
+
+
+
+//DECLARE_LEGACY_CPU_DEVICE(TMS99110A, tms99110a);
+
+/*
+ structure with optional parameters for tms99110a_reset.
+*/
+typedef struct tms99110areset_param
+{
+ ti99xx_idle_func idle_callback;
+} tms99110areset_param;
+
+
+unsigned Dasm9900 (char *buffer, unsigned pc, int model_id, const UINT8 *oprom, const UINT8 *opram);
+
+#endif /* __TMS9900_H__ */
diff --git a/src/emu/cpu/tms9900/tms9980a.c b/src/emu/cpu/tms9900/tms9980a.c
index 5ef7444fc86..c45442602f1 100644
--- a/src/emu/cpu/tms9900/tms9980a.c
+++ b/src/emu/cpu/tms9900/tms9980a.c
@@ -1,13 +1,4 @@
/*
- generate the tms9980a/tms9981 emulator
+ This file will contain the re-implementation of the tms9980a. The
+ previous implementation can be found as tms9980al.
*/
-
-#include "emu.h"
-#include "debugger.h"
-#include "tms9900.h"
-
-#define TMS99XX_MODEL TMS9980_ID
-
-#include "99xxcore.h"
-
-DEFINE_LEGACY_CPU_DEVICE(TMS9980, tms9980a);
diff --git a/src/emu/cpu/tms9900/tms9980al.c b/src/emu/cpu/tms9900/tms9980al.c
new file mode 100644
index 00000000000..f1da2ff2bdd
--- /dev/null
+++ b/src/emu/cpu/tms9900/tms9980al.c
@@ -0,0 +1,13 @@
+/*
+ generate the tms9980a/tms9981 emulator
+*/
+
+#include "emu.h"
+#include "debugger.h"
+#include "tms9900l.h"
+
+#define TMS99XX_MODEL TMS9980_ID
+
+#include "99xxcore.h"
+
+DEFINE_LEGACY_CPU_DEVICE(TMS9980L, tms9980al);
diff --git a/src/emu/cpu/tms9900/tms9995.c b/src/emu/cpu/tms9900/tms9995.c
index 4e0c05a48a7..500db518bc0 100644
--- a/src/emu/cpu/tms9900/tms9995.c
+++ b/src/emu/cpu/tms9900/tms9995.c
@@ -1,13 +1,3289 @@
/*
- generate the tms9995 emulator
+ 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),
+ 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_wait_line.resolve(conf->wait_line, *this);
+ m_holda_line.resolve(conf->holda_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_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;
+ break;
+ case TMS9995_WP:
+ WP = (UINT16)m_state_any;
+ 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 = string.stringbuffer(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;
+ }
+ }
+}
+
+UINT16 tms9995_device::read_workspace_register_debug(int reg)
+{
+ int temp = m_icount;
+ UINT16 value = m_prgspace->read_word((WP+(reg<<1)) & 0xfffe);
+ m_icount = temp;
+ return value;
+}
+
+void tms9995_device::write_workspace_register_debug(int reg, UINT16 data)
+{
+ int temp = m_icount;
+ m_prgspace->write_word((WP+(reg<<1)) & 0xfffe, data);
+ 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_DIV,
+ MEMORY_READ, // Get W1
+ ALU_DIV, //
+ MEMORY_READ, // Get W2
+ ALU_DIV, // Check for overflow, skip next instruction if not
+ ABORT,
+ ALU_DIV, // Calculate quotient
+ MEMORY_WRITE, // Write quotient to &W1
+ ALU_DIV,
+ 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_state == false)
+ {
+ // We are in a wait state
+ set_wait_state(true);
+ 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_wait_state(false);
+ 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);
+ 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 == INPUT_LINE_NMI)
+ {
+ m_nmi_active = (state==ASSERT_LINE);
+ if (VERBOSE>3) LOG("tms9995: NMI interrupt line state=%d\n", state);
+ }
+ else
+ {
+ if (irqline == 1)
+ {
+ m_int1_active = m_flag[2] = (state==ASSERT_LINE);
+ if (VERBOSE>3) LOG("tms9995: Line INT1 state=%d\n", state);
+ }
+ else
+ {
+ if (irqline == 4)
+ {
+ 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.
+*/
+inline void tms9995_device::pulse_clock(int count)
+{
+ for (int i=0; i < count; i++)
+ {
+ m_clock_out_line(ASSERT_LINE);
+ m_clock_out_line(CLEAR_LINE);
+ m_icount--; // This is the only location where we count down the cycles.
+ if (VERBOSE>7) LOG("tms9995: pulse_clock\n");
+ 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.
+*/
+void tms9995_device::set_ready(int state)
+{
+ if (VERBOSE>5) LOG("tms9995: set READY = %d\n", state);
+ m_ready_state = (state==ASSERT_LINE);
+}
+
+void tms9995_device::abort_operation()
+{
+ command_completed();
+}
+
+/*
+ Enter or leave the wait state. We only operate the WAIT line when there is a change.
+*/
+inline void tms9995_device::set_wait_state(bool state)
+{
+ if (m_wait_state != state) m_wait_line(state? ASSERT_LINE : CLEAR_LINE);
+ m_wait_state = state;
+}
+
+/*
+ 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_flag = 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_idle = false;
+ bool check_int = (m_instruction->command != XOP && m_instruction->command != BLWP);
+ int intmask = ST & 0x000f;
+
+ if (m_lowbyte)
+ {
+ prefetch_and_decode();
+ return;
+ }
+
+ // 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
+ }
+ else
+ {
+ m_int_pending = 0;
+
+ if (m_int1_active && intmask >= 1 && check_int) m_int_pending |= PENDING_LEVEL1;
+ if (m_int_overflow && intmask >= 2 && check_int) m_int_pending |= PENDING_OVERFLOW;
+ if (m_int_decrementer && intmask >= 3 && check_int) m_int_pending |= PENDING_DECR;
+ if (m_int4_active && intmask >= 4 && check_int) m_int_pending |= PENDING_LEVEL4;
+
+ if (m_int_pending!=0)
+ {
+ if (m_idle_state)
+ {
+ m_idle_state = false;
+ if (VERBOSE>7) 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);
+ }
+ else
+ {
+ if (VERBOSE>7) LOG("tms9995: Checking interrupts ... none pending\n");
+ // No pending interrupts
+ if (check_idle && 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);
+ }
+ else
+ {
+ 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_lowbyte)
+ {
+ // Second pass for getting the instruction
+ if (VERBOSE>6) LOG("tms9995: Prefetch memory access (second pass)\n");
+ word_read();
+ 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");
+ m_lowbyte = false;
+ }
+ else
+ {
+ // 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);
+
+ m_lowbyte = false; // for mem_read
+ word_read(); // this is where the clock pulses occur
+
+ if (!m_lowbyte)
+ {
+ // Only if we got the word in one pass
+ 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);
+ }
+ }
+}
+
+/*
+ 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;
+ 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 +++++\n", 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("tms9995: Consumed %d cycles\n", cycles);
+ }
+
+ 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_wait_state = false;
+ m_lowbyte = false;
+
+ m_pass = 0;
+ m_instindex = 0;
+ m_instruction = &m_decoded[m_instindex];
+
+ // The auto-wait state generation is turned on when the READY line is cleared
+ // on RESET.
+ m_auto_wait_state = !m_ready_state;
+ if (VERBOSE>0) LOG("tms9995: RESET; automatic wait state creation is %s\n", m_auto_wait_state? "enabled":"disabled");
+ m_ready_state = true;
+ }
+ else
+ {
+ if (m_mid_flag)
+ {
+ vectorpos = 0x0008;
+ m_intmask = 0x0001;
+ PC = (PC + 2) & 0xfffe;
+ if (VERBOSE>7) LOG("tms9995: ***** MID pending\n");
+ }
+ 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;
+ 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;
+
+ 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, F0FC-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 a off-chip access
+ m_check_ready = true;
+ if (m_lowbyte)
+ {
+ // This is always the odd address
+ // With the OR we can ensure that we do not skip to an even address
+ // when we try to read a word from an odd address
+ m_current_value |= m_prgspace->read_byte(m_address | 0x0001);
+ m_lowbyte = false;
+ if (VERBOSE>3) LOG("tms9995: read external memory, second pass (address %04x, complete word = %04x)\n", m_address | 1, m_current_value);
+ m_check_hold = true;
+ }
+ else
+ {
+ UINT16 address = m_address;
+ if (m_word_access || !m_instruction->byteop)
+ {
+ // We have to come here a second time; do not advance the MPC
+ // if the address value is even
+ m_lowbyte = true;
+ m_pass = 2;
+ address &= 0xfffe;
+ m_check_hold = false;
+ }
+ m_current_value = m_prgspace->read_byte(address) << 8;
+ if (VERBOSE>3)
+ {
+ if (m_pass==2) LOG("tms9995: read external memory, first pass (address %04x, value %02x)\n", address, (m_current_value>>8)&0xff);
+ else LOG("tms9995: read external memory (single pass), address %04x, value=%04x)\n", address, m_current_value);
+ }
+ }
+ if (m_auto_wait_state)
+ {
+ if (VERBOSE>7) LOG("tms9995: Next pulse is auto wait\n");
+ pulse_clock(1);
+ }
+ }
+ pulse_clock(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;
+ }
+ 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
+ {
+ m_check_ready = true;
+
+ if (m_lowbyte)
+ {
+ // see above in mem_read
+ m_prgspace->write_byte(m_address | 0x0001, m_current_value & 0xff);
+ m_lowbyte = false;
+ if (VERBOSE>3) LOG("tms9995: write second pass (address %04x, value %02x)\n", m_address | 0x0001, m_current_value & 0xff);
+ m_check_hold = true;
+ }
+ else
+ {
+ UINT16 address = m_address;
+ if (m_word_access || !m_instruction->byteop)
+ {
+ // We have to come here a second time; do not advance the MPC
+ // if the address value is even
+ m_lowbyte = true;
+ m_pass = 2;
+ address &= 0xfffe;
+ m_check_hold = false;
+ }
+ if (VERBOSE>3)
+ {
+ if (m_pass==2) LOG("tms9995: write external memory, first pass (address %04x, value %02x)\n", address, (m_current_value>>8)&0xff);
+ else LOG("tms9995: write external memory (single pass), address %04x, value=%02x\n", address, (m_current_value>>8)&0xff);
+ }
+ m_prgspace->write_byte(address, (m_current_value >> 8)& 0xff);
+ }
+
+ if (m_auto_wait_state)
+ {
+ if (VERBOSE>7) LOG("tms9995: Next pulse is auto wait\n");
+ pulse_clock(1);
+ }
+ }
+ pulse_clock(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 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 ((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>5) LOG("tms9995: decrementer reached 0\n");
+ m_decrementer_value = m_starting_count_storage_register;
+ if (m_flag[1]==true)
+ {
+ 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_lowbyte = false;
+ 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_lowbyte = false;
+ 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_lowbyte = false;
+ 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_lowbyte = false;
+}
+
+/**************************************************************************
+ 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;
+ 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;
+ 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;
+ 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
+ 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, d;
+ INT32 w;
+
+ 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;
+ d = m_source_value;
+
+ // Now check for overflow
+ // Seems a bit complex, and don't ask me how they did it in the chip.
+ // The basic idea is that the division of a 32-bit number by a 16-bit
+ // number must result in a 16-bit quotient and a remainder, AND the
+ // quotient must have positive sign when the signs of the dividend and
+ // the divisor are equal. When any of these conditions are not met, an
+ // overflow is indicated.
+
+ // Unlike with the unsigned division we cannot tell whether we will
+ // run into an overflow before we have the complete dividend value.
+ // It might be easier to just try and divide, but the real machine
+ // requires much less cycles when there is an overflow, so it seems as
+ // if this is tested before the algorithm starts.
+
+ if ((w1 & 0x8000)==0) // positive dividend
+ {
+ if ((d & 0x8000)==0) // positive divisor
+ {
+ if ((d & 1)==0) // even divisor
+ {
+ if (w1 < d/2) overflow = false;
+ }
+ else // odd divisor
+ {
+ if ((w1 < (d-1)/2) || (w1 == (d-1)/2 && w2 < 0x8000)) overflow = false;
+ }
+ }
+ else // negative divisor
+ {
+ d = -d;
+ if ((d & 1)==0) // even divisor
+ {
+ if ((w1 < d/2) || (w1 == d/2 && w2 < d)) overflow = false;
+ }
+ else // odd divisor
+ {
+ if ((w1 < (d+1)/2) || (w1 == (d+1)/2 && w2 < 0x8000+d)) overflow = false;
+ }
+ }
+ }
+ else // negative dividend
+ {
+ w1 = -w1;
+ if ((d & 0x8000)==0) // positive divisor
+ {
+ if ((d & 1)==0) // even divisor
+ {
+ if ((w1 < d/2+1) || (w1 == d/2+1 && w2 > (-d))) overflow = false;
+ }
+ else // odd divisor
+ {
+ if ((w1 < (d+1)/2) || (w1 == (d+1)/2 && w2 > 0x8000-d)) overflow = false;
+ }
+ }
+ else // negative divisor
+ {
+ d = -d;
+ if ((d & 1)==0) // even divisor
+ {
+ if ((w1 < d/2) || (w1 == d/2 && w2 > 0)) overflow = false;
+ }
+ else // odd divisor
+ {
+ if ((w1 < (d+1)/2) || (w1 == (d+1)/2 && w2 > 0x8000)) overflow = false;
+ }
+ }
+ }
+ 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
+ w = (m_value_copy << 16) | m_current_value;
+ // Do the calculation
+ m_current_value = (UINT16)(w / (INT16)m_source_value);
+ m_value_copy = (UINT16)(w % (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.
+ d = m_value_copy;
+ while (d != 0)
+ {
+ d = (d >> 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)
+ 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);
+ }
+ 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>7) LOG("tms9995: ST = %04x\n", ST);
+ pulse_clock(1); // needs one more than LWPI
+ }
+ else
+ {
+ WP = m_current_value;
+ if (VERBOSE>7) LOG("tms9995: 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;
+ 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;
+ 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;
+ m_address -= 2; // R13
+ break;
+ case 3:
+ WP = m_current_value;
+ n = 1;
+ 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);
+ 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;
+ 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;
+ 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; // 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;
+ 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 (5): 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_reset)
+ {
+ if (VERBOSE>5) LOG("tms9995: interrupt service (5): RESET completed\n");
+ // We came from the RESET interrupt
+ m_reset = false;
+ ST &= 0x01ff;
+ m_mid_flag = 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;
+}
-#include "emu.h"
-#include "debugger.h"
-#include "tms9900.h"
+UINT32 tms9995_device::disasm_max_opcode_bytes() const
+{
+ return 6;
+}
-#define TMS99XX_MODEL TMS9995_ID
+offs_t tms9995_device::disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options)
+{
+ return Dasm9900(buffer, pc, TMS9995_ID, oprom, opram);
+}
-#include "99xxcore.h"
-DEFINE_LEGACY_CPU_DEVICE(TMS9995, tms9995);
+const device_type TMS9995 = &device_creator<tms9995_device>;
diff --git a/src/emu/cpu/tms9900/tms9995.h b/src/emu/cpu/tms9900/tms9995.h
new file mode 100644
index 00000000000..b21b0d087f0
--- /dev/null
+++ b/src/emu/cpu/tms9900/tms9995.h
@@ -0,0 +1,433 @@
+/*
+ tms9995.h
+
+ See tms9995.c for documentation
+ Also see tms9900.h for types of TMS9xxx processors.
+*/
+
+#ifndef __TMS9995_H__
+#define __TMS9995_H__
+
+#include "emu.h"
+#include "debugger.h"
+
+enum
+{
+ TI990_10_ID = 1,
+ TMS9900_ID = 3,
+ TMS9940_ID = 4,
+ TMS9980_ID = 5,
+ TMS9985_ID = 6,
+ TMS9989_ID = 7,
+ TMS9995_ID = 9,
+ TMS99000_ID = 10,
+ TMS99105A_ID = 11,
+ TMS99110A_ID = 12
+};
+
+#define MCFG_TMS9995_ADD(_tag, _device, _clock, _prgmap, _iomap, _config) \
+ MCFG_DEVICE_ADD(_tag, _device, _clock / 4.0) \
+ MCFG_DEVICE_PROGRAM_MAP(_prgmap) \
+ MCFG_DEVICE_IO_MAP(_iomap) \
+ MCFG_DEVICE_CONFIG(_config)
+
+enum
+{
+ IDLE_OP = 2,
+ RSET_OP = 3,
+ CKOF_OP = 5,
+ CKON_OP = 6,
+ LREX_OP = 7
+};
+
+/*
+ Configuration for the TMS9995. The connections are provided by the
+ main board which contains the processor.
+*/
+typedef struct _tms9995_config
+{
+ devcb_write8 external_callback;
+ devcb_write_line iaq_line;
+ devcb_write_line clock_out;
+ devcb_write_line wait_line;
+ devcb_write_line holda_line;
+ int mode;
+ int overflow;
+} tms9995_config;
+
+#define TMS9995_CONFIG(name) \
+ const tms9995_config(name) =
+
+enum
+{
+ NO_INTERNAL_RAM = 0,
+ INTERNAL_RAM,
+ NO_OVERFLOW_INT = 0,
+ OVERFLOW_INT
+};
+
+class tms9995_device : public cpu_device
+{
+public:
+ tms9995_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock);
+
+ // READY input line. When asserted (high), the memory is ready for data exchange.
+ // We chose to use a direct method instead of a delegate to keep performance
+ // footprint low; this method may be called very frequently.
+ void set_ready(int state);
+
+ // HOLD input line. When asserted (low), the CPU is requested to release the
+ // data and address bus and enter the HOLD state. The entrance of this state
+ // is acknowledged by the HOLDA output line.
+ void set_hold(int state);
+
+protected:
+ // device-level overrides
+ virtual void device_start();
+ virtual void device_stop();
+ virtual void device_reset();
+
+ // device_execute_interface overrides
+ virtual UINT32 execute_min_cycles() const;
+ virtual UINT32 execute_max_cycles() const;
+ virtual UINT32 execute_input_lines() const;
+ virtual void execute_set_input(int irqline, int state);
+ virtual void execute_run();
+
+ // device_disasm_interface overrides
+ virtual UINT32 disasm_min_opcode_bytes() const;
+ virtual UINT32 disasm_max_opcode_bytes() const;
+ virtual offs_t disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options);
+
+ const address_space_config* memory_space_config(address_spacenum spacenum) const;
+
+private:
+ // State / debug management
+ UINT16 m_state_any;
+ static const char* s_statename[];
+ void state_import(const device_state_entry &entry);
+ void state_export(const device_state_entry &entry);
+ void state_string_export(const device_state_entry &entry, astring &string);
+ UINT16 read_workspace_register_debug(int reg);
+ void write_workspace_register_debug(int reg, UINT16 data);
+
+ // TMS9995 hardware registers
+ UINT16 WP; // Workspace pointer
+ UINT16 PC; // Program counter
+ UINT16 ST; // Status register
+
+ // The TMS9995 has a prefetch feature which causes a wrong display of the PC.
+ // We use this additional member for the debugger only.
+ UINT16 PC_debug;
+
+ // 256 bytes of onchip memory
+ UINT8 m_onchip_memory[256];
+
+ const address_space_config m_program_config;
+ const address_space_config m_io_config;
+ address_space* m_prgspace;
+ address_space* m_cru;
+
+ // Variant of the TMS9995 without internal RAM and decrementer
+ bool m_mp9537;
+
+ // Processor states
+ bool m_idle_state;
+ bool m_nmi_state;
+ bool m_irq_state;
+ bool m_ready_state;
+ bool m_wait_state;
+ bool m_hold_state;
+
+ // Auto-wait state generation
+ bool m_auto_wait_state;
+
+ // Cycle counter
+ int m_icount;
+
+ // The next memory access will address the low byte
+ bool m_lowbyte;
+
+ // Check the READY line?
+ bool m_check_ready;
+
+ // Check the HOLD line
+ bool m_check_hold;
+
+ // For multi-pass operations. For instance, memory word accesses are
+ // executed as two consecutive byte accesses. CRU accesses are repeated
+ // single-bit accesses.
+ int m_pass;
+
+ // For parity operations
+ int m_parity;
+
+ // For Format 1 instruction; determines whether the next operand address
+ // derivation is for the source or address operand
+ bool m_get_destination;
+
+ // Used for situations when a command is byte-oriented, but the memory access
+ // must be word-oriented. Example: MOVB *R1,R0; we must read the full word
+ // from R1 to get the address.
+ bool m_word_access;
+
+ // Interrupt handling
+ bool m_nmi_active;
+ bool m_int1_active;
+ bool m_int4_active;
+ bool m_int_decrementer;
+ bool m_int_overflow;
+
+ bool m_reset;
+ bool m_mid_flag;
+
+ // Flag field
+ int m_int_pending;
+
+ // The TMS9995 is capable of raising an internal interrupt on
+ // arithmetic overflow, depending on the status register Overflow Enable bit.
+ // However, the specs also say that this feature is non-functional in the
+ // currently available chip. Thus we have an option to turn it off so that
+ // software will not change its behavior on overflows.
+ bool m_check_overflow;
+
+ // Service pending interrupts
+ void service_interrupt();
+
+ // Issue clock pulses. The TMS9995 uses one (output) clock cycle per machine cycle.
+ inline void pulse_clock(int count);
+
+ // Signal the wait state via the external line
+ inline void set_wait_state(bool state);
+
+ // Signal the wait state via the external line
+ inline void set_hold_state(bool state);
+
+ // Only used for the DIV(S) operations. It seems sufficient to let the
+ // command terminate at this point, so this method just calls command_terminated.
+ void abort_operation(void);
+
+ // Decode the given 16-bit value which has been retrieved by a prefetch or
+ // during an X operation.
+ void decode(UINT16 inst);
+
+ // Store the interrupt mask part of the ST. This is used when processing
+ // an interrupt, passing the new mask from the service_interrupt part to
+ // the program part.
+ int m_intmask;
+
+ // Stored address
+ UINT16 m_address;
+
+ // Stores the recently read word or the word to be written
+ UINT16 m_current_value;
+
+ // Stores the value of the source operand in multi-operand instructions
+ UINT16 m_source_value;
+
+ // During indexed addressing, this value is added to get the final address value.
+ UINT16 m_address_add;
+
+ // During indirect/auto-increment addressing, this copy of the address must
+ // be preserved while writing the new value to the register.
+ UINT16 m_address_saved;
+
+ // Another copy of the address
+ UINT16 m_address_copy;
+
+ // Copy of the value
+ UINT16 m_value_copy;
+
+ // Stores the recent register number. Only used to pass the register
+ // number during the operand address derivation.
+ int m_regnumber;
+
+ // Stores the number of bits or shift operations
+ int m_count;
+
+ // ============== Decrementer =======================
+ void trigger_decrementer();
+
+ // Start value
+ UINT16 m_starting_count_storage_register;
+
+ // Current decrementer value.
+ UINT16 m_decrementer_value;
+
+ // ============== CRU support ======================
+
+ UINT16 m_cru_address;
+ UINT16 m_cru_value;
+ bool m_cru_first_read;
+ int m_cru_bits_left;
+ UINT32 m_cru_read;
+
+ // CPU-internal CRU flags
+ bool m_flag[16];
+
+ // ============== Prefetch support =====================
+
+ typedef struct _decoded_instruction
+ {
+ UINT16 IR;
+ UINT16 command;
+ const UINT8* program;
+ bool byteop;
+ int state;
+ } decoded_instruction;
+
+ int m_instindex;
+
+ // We implement the prefetch mechanism by two separate datasets for
+ // the decoded commands. When the previous command has completed, the
+ // pointer is just switched to the other one.
+ tms9995_device::decoded_instruction m_decoded[2];
+ tms9995_device::decoded_instruction* m_instruction;
+
+ // ================ Microprogram support ========================
+
+ // Set up lookup table
+ void build_command_lookup_table();
+
+ // Sequence of micro-operations
+ typedef const UINT8* microprogram;
+
+ // Method pointer
+ typedef void (tms9995_device::*ophandler)(void);
+
+ // Opcode list entry
+ typedef struct _tms_instruction
+ {
+ UINT16 opcode;
+ int id;
+ int format;
+ microprogram prog; // Microprogram
+ } tms_instruction;
+
+ // Lookup table entry
+ typedef struct _lookup_entry
+ {
+ struct _lookup_entry *next_digit;
+ const tms_instruction *entry;
+ } lookup_entry;
+
+ // Pointer to the lookup table; the entry point for searching the command
+ lookup_entry* m_command_lookup_table;
+
+ // List of allocated tables (used for easy clean-up on exit)
+ lookup_entry* m_lotables[32];
+
+ // List of pointers for micro-operations
+ static const tms9995_device::ophandler s_microoperation[];
+
+ static const tms9995_device::tms_instruction s_command[];
+
+ // Micro-operation program counter (as opposed to the program counter PC)
+ int MPC;
+
+ // Calling microprogram (used when data derivation is called)
+ const UINT8* m_caller;
+ int m_caller_MPC;
+
+ // Table of microprograms
+ static const microprogram mp_table[];
+
+ // Used to display the number of consumed cycles in the log.
+ int m_first_cycle;
+
+ // Status register update
+ inline void set_status_bit(int bit, bool state);
+ inline void compare_and_set_lae(UINT16 value1, UINT16 value2);
+ void set_status_parity(UINT8 value);
+
+ // Micro-operation declarations
+ void int_prefetch_and_decode();
+ void prefetch_and_decode();
+ void mem_read();
+ void mem_write();
+ inline void word_read();
+ inline void word_write();
+ void operand_address_subprogram();
+ void increment_register();
+ void indexed_addressing();
+ void set_immediate();
+ void return_with_address();
+ void return_with_address_copy();
+ void cru_input_operation();
+ void cru_output_operation();
+ void command_completed();
+ void next_command();
+
+ // ALU operations for specific commands
+ void alu_nop();
+ void alu_add_s_sxc();
+ void alu_b();
+ void alu_blwp();
+ void alu_c();
+ void alu_ci();
+ void alu_clr_seto();
+ void alu_divide();
+ void alu_divide_signed();
+ void alu_external();
+ void alu_f3();
+ void alu_imm_arithm();
+ void alu_jump();
+ void alu_ldcr();
+ void alu_li();
+ void alu_limi_lwpi();
+ void alu_lst_lwp();
+ void alu_mov();
+ void alu_multiply();
+ void alu_multiply_signed();
+ void alu_rtwp();
+ void alu_sbo_sbz();
+ void alu_shift();
+ void alu_single_arithm();
+ void alu_stcr();
+ void alu_stst_stwp();
+ void alu_tb();
+ void alu_x();
+ void alu_xop();
+ void alu_int();
+
+ // ================ Connections ====================
+
+ // Trigger external operation. This is achieved by putting a special value in
+ // the most significant three bits of the data bus and pulsing the CRUCLK line.
+ // Accordingly, we have
+ //
+ // D0 D1 D2
+ // 0 0 0 normal CRU access
+ // 0 1 0 IDLE
+ // 0 1 1 RSET
+ // 1 0 1 CKON
+ // 1 1 0 CKOF
+ // 1 1 1 LREX
+ //
+ // We could realize this via the CRU access as well, but the data bus access
+ // is not that simple to emulate. For the sake of homogenity between the
+ // chip emulations we use a dedicated callback.
+ devcb_resolved_write8 m_external_operation;
+
+ // Signal to the outside world that we are now getting an instruction (IAQ).
+ // In the real hardware this line is shared with the HOLDA line, and the
+ // /MEMEN line is used to decide which signal we have on the line. We do not
+ // emulate the /MEMEN line, so we have to use two separate lines.
+ devcb_resolved_write_line m_iaq_line;
+
+ // Clock output.
+ devcb_resolved_write_line m_clock_out_line;
+
+ // Wait output. When asserted (high), the CPU is in a wait state.
+ devcb_resolved_write_line m_wait_line;
+
+ // Asserted when the CPU is in a HOLD state
+ devcb_resolved_write_line m_holda_line;
+};
+
+unsigned Dasm9900(char *buffer, unsigned pc, int model_id, const UINT8 *oprom, const UINT8 *opram);
+
+// device type definition
+extern const device_type TMS9995;
+
+#endif /* __TMS9995_H__ */
diff --git a/src/emu/cpu/tms9900/tms9995l.c b/src/emu/cpu/tms9900/tms9995l.c
new file mode 100644
index 00000000000..9420605af2a
--- /dev/null
+++ b/src/emu/cpu/tms9900/tms9995l.c
@@ -0,0 +1,13 @@
+/*
+ generate the tms9995 emulator
+*/
+
+#include "emu.h"
+#include "debugger.h"
+#include "tms9900l.h"
+
+#define TMS99XX_MODEL TMS9995_ID
+
+#include "99xxcore.h"
+
+DEFINE_LEGACY_CPU_DEVICE(TMS9995L, tms9995l);