diff options
author | 2012-06-07 20:14:04 +0000 | |
---|---|---|
committer | 2012-06-07 20:14:04 +0000 | |
commit | 93a03892728abab8479dd0bc872537d540ff375d (patch) | |
tree | dcd25508f7e14de0e3ec1447deccbea8451c8bc0 /src/emu | |
parent | 531a86f2726e8f5173fe8881fd754d2a2d0d243c (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.mak | 24 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/99xxcore.h | 46 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/ti990_10.c | 13 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/ti990_10l.c | 13 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/tms9900.c | 2519 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/tms9900.h | 531 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/tms9900l.c | 15 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/tms9900l.h | 210 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/tms9980a.c | 13 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/tms9980al.c | 13 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/tms9995.c | 3290 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/tms9995.h | 433 | ||||
-rw-r--r-- | src/emu/cpu/tms9900/tms9995l.c | 13 |
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); |