summaryrefslogtreecommitdiffstats
path: root/src/devices/cpu/tms9900/tms9995.h
blob: 940ec0f43ee4f0cdba0f85005827931477874638 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
// license:BSD-3-Clause
// copyright-holders:Michael Zapf
/*
  tms9995.h

  See tms9995.c for documentation
  Also see tms9900.h for types of TMS99xx processors.
*/

#ifndef MAME_CPU_TMS9995_TMS9995_H
#define MAME_CPU_TMS9995_TMS9995_H

#pragma once

#include "debugger.h"
#include "tms99com.h"

// device type definition
DECLARE_DEVICE_TYPE(TMS9995, tms9995_device)
DECLARE_DEVICE_TYPE(TMS9995_MP9537, tms9995_mp9537_device)

enum
{
	INT_9995_RESET = 0,
	INT_9995_INTREQ = 1,
	INT_9995_INT1 = 2,
	INT_9995_INT4 = 3
};

#define MCFG_TMS9995_EXTOP_HANDLER( _extop) \
	devcb = &downcast<tms9995_device &>(*device).set_extop_callback(DEVCB_##_extop);

#define MCFG_TMS9995_IAQ_HANDLER( _iaq )    \
	devcb = &downcast<tms9995_device &>(*device).set_iaq_callback(DEVCB_##_iaq);

#define MCFG_TMS9995_CLKOUT_HANDLER( _clkout ) \
	devcb = &downcast<tms9995_device &>(*device).set_clkout_callback(DEVCB_##_clkout);

#define MCFG_TMS9995_HOLDA_HANDLER( _holda ) \
	devcb = &downcast<tms9995_device &>(*device).set_holda_callback(DEVCB_##_holda);

#define MCFG_TMS9995_DBIN_HANDLER( _dbin ) \
	devcb = &downcast<tms9995_device &>(*device).set_dbin_callback(DEVCB_##_dbin);

#define MCFG_TMS9995_ENABLE_OVINT( _ovint ) \
	downcast<tms9995_device*>(device)->set_overflow_interrupt( _ovint );


class tms9995_device : public cpu_device
{
public:
	static constexpr int AS_SETOFFSET = 4;

	tms9995_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t 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.
	DECLARE_WRITE_LINE_MEMBER( ready_line );

	// 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.
	DECLARE_WRITE_LINE_MEMBER( hold_line );

	// RESET input line. Unlike the standard set_input_line, this input method
	// is synchronous and will immediately lead to a reset of the CPU.
	DECLARE_WRITE_LINE_MEMBER( reset_line );

	// Callbacks
	template<class Object> devcb_base &set_extop_callback(Object &&cb) { return m_external_operation.set_callback(std::forward<Object>(cb)); }
	template<class Object> devcb_base &set_iaq_callback(Object &&cb) { return m_iaq_line.set_callback(std::forward<Object>(cb)); }
	template<class Object> devcb_base &set_clkout_callback(Object &&cb) { return m_clock_out_line.set_callback(std::forward<Object>(cb)); }
	template<class Object> devcb_base &set_holda_callback(Object &&cb) { return m_holda_line.set_callback(std::forward<Object>(cb)); }
	template<class Object> devcb_base &set_dbin_callback(Object &&cb) { return m_dbin_line.set_callback(std::forward<Object>(cb)); }

	// For debugger access
	uint8_t debug_read_onchip_memory(offs_t addr) { return m_onchip_memory[addr & 0xff]; };
	bool is_onchip(offs_t addrb) { return (((addrb & 0xff00)==0xf000 && (addrb < 0xf0fc)) || ((addrb & 0xfffc)==0xfffc)) && !m_mp9537; }

	void set_overflow_interrupt( int enable ) { m_check_overflow = (enable!=0); }

protected:
	tms9995_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);

	// device-level overrides
	virtual void        device_start() override;

	// device_execute_interface overrides
	virtual uint32_t      execute_min_cycles() const override;
	virtual uint32_t      execute_max_cycles() const override;
	virtual uint32_t      execute_input_lines() const override;
	virtual void        execute_set_input(int irqline, int state) override;
	virtual void        execute_run() override;

	// device_disasm_interface overrides
	virtual std::unique_ptr<util::disasm_interface> create_disassembler() override;

	virtual space_config_vector memory_space_config() const override;

	uint64_t execute_clocks_to_cycles(uint64_t clocks) const override { return clocks / 4.0; }
	uint64_t execute_cycles_to_clocks(uint64_t cycles) const override { return cycles * 4.0; }

	// Variant of the TMS9995 without internal RAM and decrementer
	bool    m_mp9537;

private:
	// State / debug management
	uint16_t  m_state_any;
	static const char* s_statename[];
	void    state_import(const device_state_entry &entry) override;
	void    state_export(const device_state_entry &entry) override;
	void    state_string_export(const device_state_entry &entry, std::string &str) const override;
	uint16_t  read_workspace_register_debug(int reg);
	void    write_workspace_register_debug(int reg, uint16_t data);

	// TMS9995 hardware registers
	uint16_t  WP;     // Workspace pointer
	uint16_t  PC;     // Program counter
	uint16_t  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_t  PC_debug;

	// 256 bytes of onchip memory
	uint8_t   m_onchip_memory[256];

	const address_space_config      m_program_config;
	const address_space_config      m_setoffset_config;
	const address_space_config      m_io_config;
	address_space*                  m_prgspace;
	address_space*                  m_sospace;
	address_space*                  m_cru;

	// Processor states
	bool    m_idle_state;
	bool    m_nmi_state;
	bool    m_hold_state;
	bool    m_hold_requested;

	// READY handling. The READY line is operated before the clock
	// pulse falls. As the ready line is only set once in this emulation we
	// keep the level in a buffer (like a latch)
	bool    m_ready_bufd;   // buffered state
	bool    m_ready;        // sampled value

	// Auto-wait state generation
	bool    m_request_auto_wait_state;
	bool    m_auto_wait;

	// Cycle counter
	int     m_icount;

	// Phase of the memory access
	int     m_mem_phase;

	// 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 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_overflow;

	bool    m_reset;
	bool    m_from_reset;
	bool    m_mid_flag;
	bool    m_mid_active;

	int     m_decrementer_clkdiv;
	bool    m_log_interrupt;

	// 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 hold state via the external line
	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_t 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_t  m_address;

	// Stores the recently read word or the word to be written
	uint16_t  m_current_value;

	// Stores the value of the source operand in multi-operand instructions
	uint16_t  m_source_value;

	// During indexed addressing, this value is added to get the final address value.
	uint16_t  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_t  m_address_saved;

	// Another copy of the address
	uint16_t  m_address_copy;

	// Copy of the value
	uint16_t  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_t  m_starting_count_storage_register;

	// Current decrementer value.
	uint16_t  m_decrementer_value;

	// ============== CRU support ======================

	uint16_t  m_cru_address;
	uint16_t  m_cru_value;
	bool    m_cru_first_read;
	int     m_cru_bits_left;
	uint32_t  m_cru_read;

	// CPU-internal CRU flags
	bool    m_flag[16];

	// ============== Prefetch support =====================

	// We implement the prefetch mechanism by two separate datasets for
	// the decoded commands. When the next instruction shall be started,
	// the contents from the pre* members are copied to the main members.

	uint16_t  IR;
	uint16_t  m_command;
	int     m_index;
	bool    m_byteop;

	uint16_t  m_pre_IR;
	uint16_t  m_pre_command;
	int     m_pre_index;
	bool    m_pre_byteop;

	// State of the currently executed instruction
	int     m_inst_state;

	// ================ Microprogram support ========================

	// Set up lookup table
	void build_command_lookup_table();

	// Sequence of micro-operations
	typedef const uint8_t* microprogram;

	// Method pointer
	typedef void (tms9995_device::*ophandler)(void);

	// Opcode list entry
	struct tms_instruction
	{
		uint16_t              opcode;
		int                 id;
		int                 format;
		microprogram        prog;       // Microprogram
	};

	// Lookup table entry
	struct lookup_entry
	{
		std::unique_ptr<lookup_entry[]> next_digit;
		int index; // pointing to the static instruction list
	};

	// Pointer to the lookup table; the entry point for searching the command
	std::unique_ptr<lookup_entry[]>   m_command_lookup_table;

	// List of pointers for micro-operations
	static const tms9995_device::ophandler s_microoperation[];

	static const tms9995_device::tms_instruction s_command[];

	// Index of the interrupt program
	int     m_interrupt_mp_index;

	// Index of the operand address derivation subprogram
	int     m_operand_address_derivation_index;

	// Micro-operation program counter (as opposed to the program counter PC)
	int     MPC;

	// Calling microprogram (used when data derivation is called)
	int     m_caller_index;
	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_t value1, uint16_t value2);
	void set_status_parity(uint8_t 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_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 homogeneity between the
	// chip emulations we use a dedicated callback.
	devcb_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_write_line   m_iaq_line;

	// Clock output.
	devcb_write_line   m_clock_out_line;

	// Asserted when the CPU is in a HOLD state
	devcb_write_line   m_holda_line;

	// DBIN line. When asserted (high), the CPU has disabled the data bus output buffers.
	devcb_write_line   m_dbin_line;
};


/*
    Variant of the TMS9995 without on-chip RAM; used in the TI-99/8 console
*/
class tms9995_mp9537_device : public tms9995_device
{
public:
	tms9995_mp9537_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
		: tms9995_device(mconfig, TMS9995_MP9537, tag, owner, clock)
	{
		m_mp9537 = true;
	}
};

#endif // MAME_CPU_TMS9995_TMS9995_H