summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/tms9901.cpp
blob: bb7148a91a63cec3980aaa182dd1f4f040c80d2a (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
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
// license:BSD-3-Clause
// copyright-holders:Michael Zapf
/****************************************************************************

    TMS9901 Programmable System Interface

                    +--------------+
               RST1*| 1   |  |   40| Vcc
             CRUOUT | 2   +--+   39| S0
             CRUCLK | 3          38| P0
              CRUIN | 4          37| P1
                 CE*| 5          36| S1
               INT6*| 6          35| S2
               INT5*| 7          34| INT7*  / P15
               INT4*| 8          33| INT8*  / P14
               INT3*| 9          32| INT9*  / P13
                Phi*|10          31| INT10* / P12
             INTREQ*|11          30| INT11* / P11
                IC3 |12          29| INT12* / P10
                IC2 |13          28| INT13* / P9
                IC1 |14          27| INT14* / P8
                IC0 |15          26| P2
                Vss |16          25| S3
               INT1*|17          24| S4
               INT2*|18          23| INT15* / P7
                 P6 |19          22| P3
                 P5 |20          21| P4
                    +--------------+

Reference: [1] TMS9901 Programmable Systems Interface Data Manual, July 1977
           [2] A. Osborne, G. Kane: Osborne 16-bit microprocessor handbook

Overview:
    TMS9901 is a support chip for TMS9900.  It handles interrupts, provides
    several I/O pins, and a timer, which is a register which
    decrements regularly and can generate an interrupt when it reaches 0.

    It communicates with the TMS9900 with the CRU bus, and with the rest of the
    world with a number of parallel I/O pins.

    I/O and timer functions should work with any other 990/99xx/99xxx CPU.
    On the other hand, interrupt handling was primarily designed for TMS9900
    and 99000 based systems: other CPUs can support interrupts, but not the 16
    distinct interrupt vectors.

    For the 9980A, the IC1-IC3 lines are connected to the IC0-IC2 lines of the
    9980A.

Pins:
    Vcc, Vss: power supply
    Phi* :    system clock (connected to TMS9900 Phi3* or TMS9980A CLKOUT*)
    RST1*:    Reset input
    CRUIN,
    CRUOUT,
    CRUCLK:   CRU bus
    CE*:      Chip enable; typically driven by a decoder for the CRU address
    S0-S4:    CRU access bits (0..31; S0 is MSB)
    INTREQ*:  Interrupt request; active (0) when an interrupt is signaled to the CPU
    IC0-IC3:  Interrupt level (0..15, IC0 is MSB)

    Three groups of I/O pins:

    Group 1: INT1*-INT6*: Interrupt inputs.
    Group 2: INT7*_P15 - INT15*_P7: Interrupt inputs or I/O ports
    Group 3: P0-P6: I/O ports

    In group 2, the interrupt inputs and I/O ports share their pins, which
    leads to mirroring in the CRU address space.

Input/Output ports:
    P0 to P15 are preconfigured as input ports. By writing a value to a port,
    it is configured as an output. Caution must be taken that the pin is not
    fed with some logic level when setting it as output, because this may
    damage the port. To reconfigure it as an input, the chip must be reset
    by the hard RST1* line or the soft RST2* operation (setting bit 15 to 0
    in clock mode). Output pins can be read and return the currently set value.

Interrupt inputs (group 1 and 2)
    The interrupt inputs (INT1*-INT15*) are sampled on each falling edge of
    the phi* clock. An interrupt mask is applied to mask away levels that
    shall not trigger an interrupt. The mask can be set using the SBO/SBZ
    commands (1=arm, 0=disarm) on each of the 15 bits.

    After each clock cycle, the TMS9901 latches the state of INT1*-INT15*
    (except those pins which are set as output pins).

    If there are some unmasked interrupt bits, INTREQ* is asserted and the code
    of the lowest active interrupt is placed on IC0-IC3. The interrupt line
    and the level lines should be connected to the respective inputs of the
    TMS9900 or TMS9980A.

Group 2 pins (shared I/O and INT*)
    Pins of group 2 are shared between the I/O ports and the interrupt inputs.
    Internally, they are treated as different signals: There are interrupt
    mask bits for INT7*..INT15*, and there is a CRU bit for each of the I/O
    ports. For example, INT7* can be read by bit 7, and the same pin can be
    read as P15 via bit 31. When setting bit 7 to 1, the INT7* input is armed
    and triggers an interrupt at level 7 when asserted.

    In contrast, when writing to bit 31, P15 (same pin) is configured as an
    output, and the written value appears on the pin. When the port is set
    as output, the interrupt input on the shared pin is deactivated.

    According to [1], the interrupt mask should be set to 0 for those group 2
    pins that are used as input/output pins so that no unwanted interrupts are
    triggered.

Clock mode:
    The "clock mode" is entered by setting bit 0 to 1; setting to 0 enters
    "interrupt mode". The internal clock is a 14-bit decrementer that
    counts down by 1 every 64 clock ticks. On entering clock mode, the current
    value of the decrementer is copied to the clock read register and can be
    read by the CRU bits 1 to 14. Writing to these CRU bits modifies the
    respective bit of the clock register that serves as the start value. Every
    time a bit is written, the decrementer is loaded with the current clock
    register value.
                           Interrupt
                              ^
                              |
    [Clock register] -> [Decrementer] -> [Clock read register]
         ^                                         |
         |                                         v
         +--<---  CRU write         CRU read---<---+

    The specs somewhat ambiguously say that "writing a non-zero value enables the clock"
    and "the clock is disabled by RST1* or by writing a zero value into the clock register".
    Tests show that when a 0 has been written, the chip still counts down from
    0x3FFF to 0. However, no interrupt is raised when reaching 0, so "enable"
    or "disable" most likely refer to the interrupt.

    When enabled, the clock raises an interrupt level 3 when reaching 0,
    overriding the input from the INT3* input. CRU bit 3 is the mask bit for
    both clock and INT3* input. Writing any value to it changes the mask bit,
    and as a side effect it also clears the clock interrupt.

    According to [2], the clock mode is temporarily left when the CRU bits of
    the I/O ports (bits 16-31) are accessed. Thus, the 9901 can control its
    I/O ports even when it has been set to clock mode before. (Keep in mind
    that clock mode simply means to access the clock register; the clock is
    counting all the time.)

    Raphael Nabet, 2000-2004
    Michael Zapf

*****************************************************************************/

#include "emu.h"
#include "tms9901.h"

#include <math.h>

#define LOG_GENERAL  (1U << 0)
#define LOG_PINS     (1U << 1)
#define LOG_MASK     (1U << 2)
#define LOG_MODE     (1U << 3)
#define LOG_INT      (1U << 4)
#define LOG_CLOCK    (1U << 5)

#define VERBOSE ( LOG_GENERAL )
#include "logmacro.h"

/*
    Constructor
*/
tms9901_device::tms9901_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, TMS9901, tag, owner, clock),
	m_int_level(0),
	m_last_level(0),
	m_int_mask(0),
	m_int_pending(false),
	m_poll_lines(false),
	m_clockdiv(0),
	m_timer_int_pending(false),
	m_read_port(*this),
	m_write_p{{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this},{*this}},
	m_interrupt(*this)
{
}

/*
    Determine the most significant interrupt (lowest number)
*/
void tms9901_device::prioritize_interrupts()
{
	// Prioritizer: Search for the interrupt with the highest level
	bool found = false;

	// Skip the rightmost bit
	uint16_t masked_ints = m_int_line;

	// Is the clock enabled?
	if (m_clock_register != 0)
	{
		// Disable the actual INT3* input
		masked_ints &= ~(1<<INT3);

		// Do we have a clock interrupt?
		if (m_timer_int_pending)
		{
			masked_ints |= (1<<INT3);
			LOGMASKED(LOG_INT, "INT3 (timer) asserted\n");
		}
	}

	m_int_level = 1;
	masked_ints = (masked_ints & m_int_mask)>>1;

	while ((masked_ints!=0) && !found)
	{
		// If INTn is set, stop searching. Consider, however, that
		// within INT7-INT15, those pins configured as outputs are not sampled
		// (shared pins with P15-P7)

		if ((masked_ints & 1) && ((m_int_level < 7) || !is_output(22-m_int_level)))
			found = true;
		else
		{
			m_int_level++;
			masked_ints >>= 1;
		}
	}

	if (!found) m_int_level = 15;

	m_int_pending = found;

	// Only for asynchronous emulation
	if (clock()!=0) signal_int();
}

bool tms9901_device::is_output(int p)
{
	return BIT(m_pio_direction, p);
}

bool tms9901_device::output_value(int p)
{
	return BIT(m_pio_output, p);
}

void tms9901_device::set_and_latch_output(int p, bool val)
{
	set_bit(m_pio_direction, p, true);
	set_bit(m_pio_output, p, val);
	m_write_p[p](val);
}

void tms9901_device::set_bit(uint16_t& bitfield, int pos, bool val)
{
	if (val) bitfield |= (1<<pos);
	else bitfield &= ~(1<<pos);
}

void tms9901_device::signal_int()
{
	if (m_int_level == m_last_level)
		return;

	m_last_level = m_int_level;

	if (m_int_pending)
	{
		LOGMASKED(LOG_INT, "Triggering interrupt, level %d\n", m_int_level);
		if (!m_interrupt.isnull())
			m_interrupt(ASSERT_LINE);
	}
	else
	{
		LOGMASKED(LOG_INT, "Clear all interrupts\n");
		if (!m_interrupt.isnull())
			m_interrupt(CLEAR_LINE);  //Spec: INTREQ*=1 <=> IC0,1,2,3 = 1111
	}
}

/*
    Signal an interrupt line change to the 9901.
    The real circuit samples all active interrupt inputs on every falling edge
    of the phi* clock, which is very inefficient to emulate.

    Accordingly, we let the interrupt producer calls this method and
    so push the interrupt line change.

    state == CLEAR_LINE: INTn* is inactive (high)
    state == ASSERT_LINE: INTn* is active (low)

    n=1..15
*/
void tms9901_device::set_int_line(int n, int state)
{
	if ((n >= 1) && (n <= 15))
	{
		set_bit(m_int_line, n, state==ASSERT_LINE);
		prioritize_interrupts();
	}
}

/*----------------------------------------------------------------
    TMS9901 CRU interface.
----------------------------------------------------------------*/

/*
    Read a bit from tms9901.

     Bit     Meaning
     ---------------
     0       Control bit (0=Interrupt mode, 1=Clock mode)
     1..14   /INTn  (int mode), CLKn (clock mode)
     15      /INT15 (int mode), /INTREQ (clock mode)
     16..31  P0...P15 input

     Reading an output port delivers the latched output value.

     Ports P7 to P15 share pins with the interrupt inputs /INT15 to /INT7
     (in this order). When configured as outputs, reading returns the latched
     values.
*/
READ8_MEMBER( tms9901_device::read )
{
	int crubit = offset & 0x01f;

	if (crubit == 0)
		return m_clock_mode? 1 : 0;

	if (crubit > 15)
	{
		// I/O lines
		if (is_output(crubit-16))
			return output_value(crubit-16);
		else
		{
			// Positive logic; should be 0 if there is no connection.
			if (m_read_port.isnull()) return 0;
			return m_read_port((crubit<=P6)? crubit : P6+P0-crubit);
		}
	}

	// If we are here, crubit=1..15
	if (m_clock_mode)
	{
		if (crubit == 15)    // bit 15 in clock mode = /INTREQ
			return (m_int_pending)? 0 : 1;

		return BIT(m_clock_read_register, crubit-1);
	}
	else
	{
		// We trust the read_port method to deliver the same INTx levels that
		// have been signaled via the set_int_line method.
		// Thus, those levels must be latched by the component that hosts
		// this 9901. Alternatively, use the interrupt line polling
		// which has a bad impact on performance.
		if (crubit>INT6 && is_output(22-crubit))
			return output_value(22-crubit);
		else
			return m_read_port.isnull()? 1 : m_read_port(crubit);
	}
}


/*
    Write one bit to the tms9901.

     Bit     Meaning
     ----------------------------------------------------------
     0       0=Interrupt mode, 1=Clock mode
     1..14   Clock mode: Set CLKn; Interrupt mode: Set Mask n
     15      Clock mode: /RST2; Interrupt mode: Set Mask 15
     16..31  Set P(n-16) as output, latch value, and output it
*/
WRITE8_MEMBER( tms9901_device::write )
{
	data &= 1;  // clear extra bits
	int crubit = offset & 0x001f;

	if (crubit >= 16)
	{
		LOGMASKED(LOG_PINS, "Output on P%d = %d\n", crubit-16, data);
		set_and_latch_output(crubit-P0, data);
		return;
	}

	switch (crubit)
	{
	case 0:
		// Write to control bit (CB)
		m_clock_mode = (data!=0);
		LOGMASKED(LOG_MODE, "Enter %s mode\n", m_clock_mode? "clock" : "interrupt");

		if (m_clock_mode)
		{
			// we are switching to clock mode: latch the current value of
			// the decrementer register
			m_clock_read_register = m_decrementer_value;
			LOGMASKED(LOG_MODE, "Clock setting = %d\n", m_clock_read_register);
		}
		break;

	case 15:
		if (m_clock_mode)
		{
			// In clock mode, bit 15 is /RST2
			if (data == 0) soft_reset();
		}
		else
		{
			set_bit(m_int_mask, 15, data!=0);
			LOGMASKED(LOG_MASK, "/INT15 is %s\n", data? "enabled" : "disabled");
			prioritize_interrupts();     // changed interrupt state
		}
		break;

	default:
		// Bits 1..14
		if (m_clock_mode)
		{
			// Modify clock interval
			set_bit(m_clock_register, crubit-1, data!=0);

			// Reset clock timer (page 8)
			m_decrementer_value = m_clock_register;

			LOGMASKED(LOG_CLOCK, "Clock register = %04x\n", m_clock_register);
		}
		else
		{
			// Modify interrupt enable mask
			set_bit(m_int_mask, crubit, data!=0);

			// [1] sect 2.5: "When the clock interrupt is active, the clock mask
			// (mask bit 3) must be written into (with either a "0" or "1")
			// to clear the interrupt."
			if (crubit == 3)
				m_timer_int_pending = false;

			LOGMASKED(LOG_MASK, "/INT%d is %s\n", crubit, data? "enabled" : "disabled");
			prioritize_interrupts();
		}
		break;
	}
}

/*
    Timer callback
    Decrementer counts down the value set in clock mode; when it reaches 0,
    raises an interrupt and resets to the start value
*/
void tms9901_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	if (id==DECREMENTER) // we have only that one
	{
		timer_clock_in(ASSERT_LINE);
		timer_clock_in(CLEAR_LINE);
	}
}

void tms9901_device::timer_clock_in(line_state clk)
{
	if (clk == ASSERT_LINE)
	{
		m_decrementer_value = (m_decrementer_value - 1) & 0x3FFF;
		LOGMASKED(LOG_CLOCK, "Clock = %04x\n", m_decrementer_value);
		if (m_decrementer_value==0)
		{
			if (m_clock_register != 0)
			{
				LOGMASKED(LOG_INT, "Timer expired\n");
				m_timer_int_pending = true;         // decrementer interrupt requested
				prioritize_interrupts();
			}
			m_decrementer_value = m_clock_register;
		}
	}
}

/*
    Synchronous clock input. This may be used for systems which have
    a CLK line controlled by the CPU, like the TMS99xx systems.
    In that case, clock is set to 0.
*/
WRITE_LINE_MEMBER( tms9901_device::phi_line )
{
	if (state==ASSERT_LINE)
	{
		// Divider by 64
		m_clockdiv = (m_clockdiv+1) & 0x3f;
		if (m_clockdiv==0)
		{
			timer_clock_in(ASSERT_LINE);

			// We signal the interrupt in sync with the clock line
			signal_int();

			// For the next phi assert
			// MZ: This costs a lot of performance for a minimum of benefit.
			if (m_poll_lines) sample_interrupt_inputs();
		}
		else
		{
			if (m_clockdiv==32)
				timer_clock_in(CLEAR_LINE);
		}
	}
}

/*
    All unmasked interrupt ports are sampled at the rising edge of phi.
    Doing it this way (also for performance issues): For each mask bit 1,
    fetch the pin level. Stop at the first asserted INT line.

    Good idea in terms of emulation, bad in terms of performance. The Geneve
    9640 bench dropped from 680% to 180%. Not recommended to use, except in
    very special situations. Enable by calling set_poll_int_lines(true).
*/
void tms9901_device::sample_interrupt_inputs()
{
	int mask = m_int_mask;
	m_int_level = 0;
	m_int_pending = false;

	while (mask != 0 && !m_int_pending)
	{
		m_int_level++;
		if ((mask & 1)!=0)
		{
			// Negative logic
			if (m_read_port(m_int_level)==0)
				m_int_pending = true;
		}
		mask >>= 1;
	}
}

void tms9901_device::soft_reset()
{
	// TMS9901 soft reset (RST2*)
	// Spec: "Writing a 0 to bit 15 while in the clock mode executes a soft reset on the I/O pins.
	// [...] RST2* will program all ports to the input mode"
	m_pio_direction = 0;
	m_pio_output = 0;

	// We assume that the interrupt mask is also reset.
	m_int_mask = 0;

	LOGMASKED(LOG_MODE, "Soft reset (RST2*)\n");
}

/*-------------------------------------------------
    device_stop - device-specific stop
-------------------------------------------------*/

void tms9901_device::device_stop()
{
}

/*-------------------------------------------------
    device_reset - device-specific reset
-------------------------------------------------*/

void tms9901_device::device_reset()
{
	do_reset();
}

/*
    RST1 input line (active low; using ASSERT/CLEAR).
*/
WRITE_LINE_MEMBER( tms9901_device::rst1_line )
{
	if (state==ASSERT_LINE) do_reset();
}

void tms9901_device::do_reset()
{
	m_timer_int_pending = false;
	soft_reset();

	// This is an interrupt level latch, positive logic (bit 0 = no int)
	// The inputs are negative logic (INTx*)
	m_int_line = 0;

	prioritize_interrupts();

	m_clock_mode = false;
	m_decrementer_value = m_clock_register = 0;
}


/*-------------------------------------------------
    device_start - device-specific startup
-------------------------------------------------*/

void tms9901_device::device_start()
{
	// Allow for using asynchronous and synchronous clocks
	if (clock() != 0)
	{
		m_decrementer = timer_alloc(DECREMENTER);
		m_decrementer->adjust(attotime::from_hz(clock() / 64.), 0, attotime::from_hz(clock() / 64.));
	}

	m_read_port.resolve();
	for (auto &cb : m_write_p)
		cb.resolve_safe();
	m_interrupt.resolve();

	m_clock_register = 0;

	save_item(NAME(m_int_line));
	save_item(NAME(m_pio_output));
	save_item(NAME(m_pio_direction));
	save_item(NAME(m_int_level));
	save_item(NAME(m_int_mask));
	save_item(NAME(m_last_level));
	save_item(NAME(m_int_pending));
	save_item(NAME(m_timer_int_pending));
	save_item(NAME(m_clock_mode));
	save_item(NAME(m_clock_register));
	save_item(NAME(m_decrementer_value));
	save_item(NAME(m_clock_read_register));
}

DEFINE_DEVICE_TYPE(TMS9901, tms9901_device, "tms9901", "TMS9901 Programmable System Interface")