summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/cpu/tms9900/tms9980a.c
diff options
context:
space:
mode:
author Michael Zapf <michael.zapf@mizapf.de>2012-06-14 15:52:32 +0000
committer Michael Zapf <michael.zapf@mizapf.de>2012-06-14 15:52:32 +0000
commitca025ce099a345f9c52aa0835a375f002e2e726d (patch)
treeda432e279d6b9b9e82943873aa87cab276dac097 /src/emu/cpu/tms9900/tms9980a.c
parent86dfb473944d5107542b3151baab898f888175ef (diff)
Added cycle-precise implementation of tms9980a; changed tms9900.c to allow for subclassing tms9900 and tms9980a from a common parent. [Michael Zapf]
Diffstat (limited to 'src/emu/cpu/tms9900/tms9980a.c')
-rw-r--r--src/emu/cpu/tms9900/tms9980a.c245
1 files changed, 243 insertions, 2 deletions
diff --git a/src/emu/cpu/tms9900/tms9980a.c b/src/emu/cpu/tms9900/tms9980a.c
index c45442602f1..f790f77ca85 100644
--- a/src/emu/cpu/tms9900/tms9980a.c
+++ b/src/emu/cpu/tms9900/tms9980a.c
@@ -1,4 +1,245 @@
/*
- This file will contain the re-implementation of the tms9980a. The
- previous implementation can be found as tms9980al.
+ Cycle-precise implementation of the TMS9980A.
+ Subclassed from tms99xx_device in tms9900.c.
+
+ +----------------+
+ /HOLD | 1 \/ 40| /MEMEN
+ HOLDA | 2 39| READY
+ IAQ | 3 38| /WE
+ LSB +- A13,CRUOUT | 4 37| CRUCLK
+ | A12 | 5 36| Vdd
+ | A11 | 6 35| Vss
+ | A10 | 7 34| CKIN
+ Address A9 | 8 33| D7 --+
+ bus A8 | 9 32| D6 |
+ | A7 |10 31| D5 Data
+ 16KiB A6 |11 30| D4 bus
+ | A5 |12 29| D3 |
+ | A4 |13 28| D2 2 * 8 bit
+ | A3 |14 27| D1 |
+ | A2 |15 26| D0 --+
+ | A1 |16 25| INT0 --+
+ MSB +-- A0 |17 24| INT1 | Interrupt levels
+ DBIN |18 23| INT2 --+
+ CRUIN |19 22| /PHI3
+ Vcc |20 21| Vbb
+ +----------------+
+
+ The TMS9980A is similar to the TMS9900, with the following differences:
+
+ - Address bus is only 14 bit wide (16 KiB)
+ - Data bus is 16 bit wide and multiplexed on 8 lines (2 bytes per access)
+ - CRU space is limited to 2048 bits (due to fewer address lines)
+ - Only three interrupt level lines, for a maximum of 8 levels.
+ - No INTREQ, RESET, and LOAD lines. All interrupts are signaled via INT0 -
+ INT2. Reset=00x, Load=010, Level1=011, Level2=100, Level3=101, Level4=110,
+ all interrupts cleared=111.
+ - Memory accesses are always 2 bytes (even address byte, odd address byte)
+ even for byte operations. Thus the 9980A, like the TMS9900, needs to
+ pre-fetch the word at the destination before overwriting it.
+ - On the cycle level both TMS9900 and TMS9980A are equal, except for the
+ additional cycles needed for memory read and write access. Accordingly,
+ the emulation shares the core and the microprograms and redefines the
+ memory access and the interrupt handling only.
+ - The 9980A has the same external instructions as the TMS9900, but it
+ indicates the command via A0, A1, and A13 (instead of A0-A2).
+
+ For pin definitions see tms9900.c
+
+ Michael Zapf, 2012
*/
+
+#include "tms9980a.h"
+
+#define LOG logerror
+#define VERBOSE 1
+
+/****************************************************************************
+ Constructor
+****************************************************************************/
+
+tms9980a_device::tms9980a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
+ : tms99xx_device(mconfig, TMS9980A, "TMS9980A", tag, 8, 14, 11, owner, clock)
+{
+}
+
+UINT16 tms9980a_device::read_workspace_register_debug(int reg)
+{
+ int temp = m_icount;
+ int addr = (WP+(reg<<1)) & 0xfffe & m_prgaddr_mask;
+ UINT16 value = (m_prgspace->read_byte(addr) << 8) | (m_prgspace->read_byte(addr+1) & 0xff);
+ m_icount = temp;
+ return value;
+}
+
+void tms9980a_device::write_workspace_register_debug(int reg, UINT16 data)
+{
+ int temp = m_icount;
+ int addr = (WP+(reg<<1)) & 0xfffe & m_prgaddr_mask;
+ m_prgspace->write_byte(addr, data>>8);
+ m_prgspace->write_byte(addr+1, data & 0xff);
+ m_icount = temp;
+}
+
+/*
+ Interrupt input. Keep in mind that the TMS9980A does not have any INTREQ
+ line but signals interrupts via IC0-IC2 only. Thus we cannot take down any
+ single interrupt; only all interrupts can be cleared at once using level 7.
+ The state parameter is actually not needed.
+*/
+void tms9980a_device::execute_set_input(int irqline, int state)
+{
+ m_irq_level = get_intlevel(state);
+
+ if (m_irq_level != 7)
+ {
+ if (m_irq_level == LOAD_INT)
+ {
+ // Clearing m_reset is a hack to prevent an initial RESET.
+ // Should fix that in tms99xx
+ m_reset = false;
+ m_load_state = true;
+ }
+ else m_irq_state = true;
+ if (VERBOSE>6) LOG("tms9980a: interrupt level=%d, ST=%04x\n", m_irq_level, ST);
+ }
+}
+
+int tms9980a_device::get_intlevel(int state)
+{
+ int level = m_get_intlevel(0) & 0x0007;
+
+ // Just to stay consistent.
+ if (state==CLEAR_LINE) level = 7;
+
+ switch (level)
+ {
+ case 0:
+ case 1:
+ level = RESET_INT;
+ m_reset = true;
+ break;
+ case 2:
+ level = LOAD_INT;
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ level = level - 2;
+ break;
+ case 7:
+ // Clear all interrupts
+ m_load_state = false;
+ m_irq_state = false;
+ if (VERBOSE>6) LOG("tms9980a: clear interrupts\n");
+ break;
+ }
+ return level;
+}
+
+/*****************************************************************************/
+
+/*
+ Memory read:
+ Clock cycles: 4 + 2W, W = number of wait states
+*/
+void tms9980a_device::mem_read()
+{
+ UINT8 value;
+ if (m_lowbyte)
+ {
+ value = m_prgspace->read_byte((m_address & m_prgaddr_mask) | 1);
+ m_current_value = m_current_value | (value & 0x00ff);
+ if (VERBOSE>7) LOG("tms9980a: memory read low byte %04x -> complete word %04x\n", (m_address & m_prgaddr_mask) | 1, m_current_value);
+ m_lowbyte = false;
+ }
+ else
+ {
+ value = m_prgspace->read_byte(m_address & 0x3ffe);
+ if (VERBOSE>7) LOG("tms9980a: memory read high byte %04x -> %02x\n", m_address & m_prgaddr_mask, value);
+ m_current_value = (value << 8) & 0xff00;
+ m_lowbyte = true;
+ m_pass = 2; // make the CPU visit this method once more
+ }
+ pulse_clock(2);
+ m_check_ready = true;
+}
+
+void tms9980a_device::mem_write()
+{
+ if (m_lowbyte)
+ {
+ m_prgspace->write_byte((m_address & 0x3ffe) | 1, m_current_value & 0xff);
+ if (VERBOSE>7) LOG("tms9980a: memory write low byte %04x <- %02x\n", (m_address & m_prgaddr_mask) | 1, m_current_value & 0xff);
+ m_lowbyte = false;
+ }
+ else
+ {
+ m_prgspace->write_byte(m_address & 0x3ffe, (m_current_value >> 8)&0xff);
+ if (VERBOSE>7) LOG("tms9980a: memory write high byte %04x <- %02x\n", m_address & m_prgaddr_mask, (m_current_value >> 8)&0xff);
+ m_lowbyte = true;
+ m_pass = 2; // make the CPU visit this method once more
+ }
+ pulse_clock(2);
+ m_check_ready = true;
+}
+
+void tms9980a_device::acquire_instruction()
+{
+ if (!m_lowbyte)
+ {
+ m_iaq_line(ASSERT_LINE);
+ m_address = PC;
+ m_first_cycle = m_icount;
+ mem_read();
+ }
+ else
+ {
+ mem_read();
+ decode(m_current_value);
+ if (VERBOSE>3) LOG("tms9980a: ===== Next operation %04x (%s) at %04x =====\n", IR, opname[m_command], PC);
+ debugger_instruction_hook(this, PC);
+ PC = (PC + 2) & 0xfffe & m_prgaddr_mask;
+ }
+ // IAQ will be cleared in the main loop
+}
+
+/**************************************************************************/
+UINT32 tms9980a_device::execute_min_cycles() const
+{
+ return 2;
+}
+
+// TODO: Compute this value, just a wild guess for the average
+UINT32 tms9980a_device::execute_max_cycles() const
+{
+ return 10;
+}
+
+UINT32 tms9980a_device::execute_input_lines() const
+{
+ return 1;
+}
+
+// clocks to cycles, cycles to clocks = id
+// execute_default_irq_vector = 0
+// execute_burn = nop
+
+// device_disasm_interface overrides
+UINT32 tms9980a_device::disasm_min_opcode_bytes() const
+{
+ return 2;
+}
+
+UINT32 tms9980a_device::disasm_max_opcode_bytes() const
+{
+ return 6;
+}
+
+offs_t tms9980a_device::disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options)
+{
+ return Dasm9900(buffer, pc, TMS9980_ID, oprom, opram);
+}
+
+const device_type TMS9980A = &device_creator<tms9980a_device>;