Added cycle-precise implementation of tms9980a; changed tms9900.c to allow for subclassing tms9900 and tms9980a from a common parent. [Michael Zapf]

This commit is contained in:
Michael Zapf 2012-06-14 15:52:32 +00:00
parent 86dfb47394
commit ca025ce099
6 changed files with 931 additions and 524 deletions

1
.gitattributes vendored
View File

@ -563,6 +563,7 @@ src/emu/cpu/tms9900/tms9900.h svneol=native#text/plain
src/emu/cpu/tms9900/tms9900l.c svneol=native#text/plain
src/emu/cpu/tms9900/tms9900l.h svneol=native#text/plain
src/emu/cpu/tms9900/tms9980a.c svneol=native#text/plain
src/emu/cpu/tms9900/tms9980a.h svneol=native#text/plain
src/emu/cpu/tms9900/tms9980al.c svneol=native#text/plain
src/emu/cpu/tms9900/tms9995.c svneol=native#text/plain
src/emu/cpu/tms9900/tms9995.h svneol=native#text/plain

View File

@ -1608,6 +1608,7 @@ ifneq ($(filter TMS9900,$(CPUS)),)
OBJDIRS += $(CPUOBJ)/tms9900
CPUOBJS += $(CPUOBJ)/tms9900/tms9900.o
CPUOBJS += $(CPUOBJ)/tms9900/tms9900l.o
CPUOBJS += $(CPUOBJ)/tms9900/tms9980a.o
CPUOBJS += $(CPUOBJ)/tms9900/tms9980al.o
CPUOBJS += $(CPUOBJ)/tms9900/tms9995.o
CPUOBJS += $(CPUOBJ)/tms9900/tms9995l.o
@ -1623,13 +1624,18 @@ $(CPUOBJ)/tms9900/tms9900l.o: $(CPUSRC)/tms9900/tms9900l.c \
$(CPUSRC)/tms9900/99xxcore.h \
$(CPUSRC)/tms9900/99xxstat.h
$(CPUOBJ)/tms9900/tms9980a.o: $(CPUSRC)/tms9900/tms9980a.c \
$(CPUSRC)/tms9900/tms9980a.h \
$(CPUSRC)/tms9900/tms9900.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/tms9995.h
$(CPUOBJ)/tms9900/tms9995l.o: $(CPUSRC)/tms9900/tms9995l.c \
$(CPUSRC)/tms9900/tms9900l.h \

File diff suppressed because it is too large Load Diff

View File

@ -47,6 +47,12 @@
#include "emu.h"
#include "debugger.h"
enum
{
LOAD_INT = -1,
RESET_INT = -2
};
enum
{
TI990_10_ID = 1,
@ -61,7 +67,7 @@ enum
TMS99110A_ID = 12
};
#define MCFG_TMS9900_ADD(_tag, _device, _clock, _prgmap, _iomap, _config) \
#define MCFG_TMS99xx_ADD(_tag, _device, _clock, _prgmap, _iomap, _config) \
MCFG_DEVICE_ADD(_tag, _device, _clock) \
MCFG_DEVICE_PROGRAM_MAP(_prgmap) \
MCFG_DEVICE_IO_MAP(_iomap) \
@ -76,7 +82,18 @@ enum
LREX_OP = 7
};
typedef struct _tms9900_config
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 ",
"*int"
};
typedef struct _tms99xx_config
{
devcb_write8 external_callback;
devcb_read8 irq_level;
@ -84,15 +101,19 @@ typedef struct _tms9900_config
devcb_write_line clock_out;
devcb_write_line wait_line;
devcb_write_line holda_line;
} tms9900_config;
} tms99xx_config;
#define TMS9900_CONFIG(name) \
const tms9900_config(name) =
#define TMS99xx_CONFIG(name) \
const tms99xx_config(name) =
class tms9900_device : public cpu_device
class tms99xx_device : public cpu_device
{
public:
tms9900_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock);
tms99xx_device(const machine_config &mconfig, device_type type, const char *name,
const char *tag, int databus_width, int prg_addr_bits, int cru_addr_bits,
device_t *owner, UINT32 clock);
~tms99xx_device();
// READY input line. When asserted (high), the memory is ready for data exchange.
void set_ready(int state);
@ -122,7 +143,23 @@ protected:
const address_space_config* memory_space_config(address_spacenum spacenum) const;
private:
// Let these methods be overloaded by the TMS9980.
virtual void mem_read(void);
virtual void mem_write(void);
virtual void acquire_instruction(void);
void decode(UINT16 inst);
const address_space_config m_program_config;
const address_space_config m_io_config;
address_space* m_prgspace;
address_space* m_cru;
virtual UINT16 read_workspace_register_debug(int reg);
virtual void write_workspace_register_debug(int reg, UINT16 data);
// Cycle counter
int m_icount;
// TMS9900 hardware registers
UINT16 WP; // Workspace pointer
UINT16 PC; // Program counter
@ -131,38 +168,72 @@ private:
// Internal register
UINT16 IR; // Instruction register
// Stored address
UINT16 m_address;
// Stores the recently read word or the word to be written
UINT16 m_current_value;
// Decoded command
UINT16 m_command;
// Issue clock pulses. Note that each machine cycle has two clock cycles.
void pulse_clock(int count);
// For multi-pass operations. For instance, memory word accesses are
// executed as two consecutive byte accesses. CRU accesses are repeated
// single-bit accesses. (Needed for TMS9980)
int m_pass;
// Data bus width. Needed for TMS9980.
int m_databus_width;
// Needed for TMS9980
bool m_lowbyte;
// Check the READY line?
bool m_check_ready;
// Max address
const UINT16 m_prgaddr_mask;
const UINT16 m_cruaddr_mask;
bool m_load_state;
bool m_irq_state;
bool m_reset;
// Determine the interrupt level using the IC0-IC2/3 lines
virtual int get_intlevel(int state);
// Interrupt level as acquired from input lines (TMS9900: IC0-IC3, TMS9980: IC0-IC2)
// We assume all values right-justified, i.e. TMS9980 also counts up by one
int m_irq_level;
// Used to display the number of consumed cycles in the log.
int m_first_cycle;
// Signal to the outside world that we are now getting an instruction
devcb_resolved_write_line m_iaq_line;
// Get the value of the interrupt level lines
devcb_resolved_read8 m_get_intlevel;
private:
// 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();
@ -176,7 +247,7 @@ private:
typedef const UINT8* microprogram;
// Method pointer
typedef void (tms9900_device::*ophandler)(void);
typedef void (tms99xx_device::*ophandler)(void);
// Opcode list entry
typedef struct _tms_instruction
@ -201,18 +272,16 @@ private:
lookup_entry* m_lotables[32];
// List of pointers for micro-operations
static const tms9900_device::ophandler s_microoperation[];
static const tms99xx_device::ophandler s_microoperation[];
// Opcode table
static const tms9900_device::tms_instruction s_command[];
static const tms99xx_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 cru_input_operation(void);
void cru_output_operation(void);
void data_derivation_subprogram(void);
void return_from_subprogram(void);
void command_completed(void);
@ -258,9 +327,6 @@ private:
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;
@ -278,27 +344,15 @@ private:
// 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;
@ -308,6 +362,10 @@ private:
// Intermediate storage for the source operand
UINT16 m_source_address;
UINT16 m_source_value;
UINT16 m_address_saved;
// Another copy of the address
UINT16 m_address_copy;
// Stores the recently read register contents
UINT16 m_register_contents;
@ -315,15 +373,15 @@ private:
// 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;
// Copy of the value
UINT16 m_value_copy;
// Another internal register, storing intermediate values
// Using 32 bits to support MPY
UINT32 m_value;
@ -336,9 +394,6 @@ private:
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
@ -363,12 +418,6 @@ private:
// chip emulations we use a dedicated callback.
devcb_resolved_write8 m_external_operation;
// 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.
@ -381,6 +430,15 @@ private:
devcb_resolved_write_line m_holda_line;
};
/*****************************************************************************/
class tms9900_device : public tms99xx_device
{
public:
tms9900_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock);
};
unsigned Dasm9900(char *buffer, unsigned pc, int model_id, const UINT8 *oprom, const UINT8 *opram);
// device type definition

View File

@ -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>;

View File

@ -0,0 +1,54 @@
/*
TMS9980A.
See tms9980a.c and tms9900.c for documentation
*/
#ifndef __TMS9980A_H__
#define __TMS9980A_H__
#include "emu.h"
#include "debugger.h"
#include "tms9900.h"
#define MCFG_TMS9980A_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)
#define TMS9980A_CONFIG(name) \
const tms9900_config(name) =
class tms9980a_device : public tms99xx_device
{
public:
tms9980a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock);
protected:
void mem_read(void);
void mem_write(void);
void acquire_instruction(void);
UINT16 read_workspace_register_debug(int reg);
void write_workspace_register_debug(int reg, UINT16 data);
UINT32 execute_min_cycles() const;
UINT32 execute_max_cycles() const;
UINT32 execute_input_lines() const;
void execute_set_input(int irqline, int state);
UINT32 disasm_min_opcode_bytes() const;
UINT32 disasm_max_opcode_bytes() const;
offs_t disasm_disassemble(char *buffer, offs_t pc, const UINT8 *oprom, const UINT8 *opram, UINT32 options);
address_space_config m_program_config80;
address_space_config m_io_config80;
int get_intlevel(int state);
};
// device type definition
extern const device_type TMS9980A;
#endif /* __TMS9995_H__ */