summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/ti99/peb/pgram.cpp
blob: c6ece9407a0927ebec862d03891c9d7c93422480 (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
// license:LGPL-2.1+
// copyright-holders:Michael Zapf
/*******************************************************************************
    PGRAM(+) Memory expansion and GROM simulator

    The PGRAM card is a battery-buffered RAM card. It also contains a circuitry
    to simulate GROMs (TMC0430), which can also be programmed, thus better
    called GRAMs.

    Memory chips of the board:

    u21: 43256LP (SRAM 32Kx8) -- GROMs 4-7
    u22:  6264LP (SRAM  8Kx8) -- GROM  3
    u23: 43256LP (SRAM 32Kx8) -- 16 KiB DSR (driver) / 16 KiB RAM

    The PGRAM+ is an extension of this card, offering 192 KiB memory:

    u21: e.g. BS62LV1027 (SRAM 128Kx8) -- GROMs 4-7 (4 banks)
    u22: 43256LP (SRAM  32Kx8) -- GROM  3 (4 banks)
    u23: 43256LP (SRAM  32Kx8) -- 16 KiB DSR (driver) / 16 KiB RAM

    The socket for u21 is already prepared for the larger package of the
    128Kx8 SRAM chip (32 pins). No modifications are needed; the smaller chip
    can simply be plugged into the larger socket. For u22, a jumper is included
    on the board for connecting the address line to the bus (PGRAM+)
    or to high level (PGRAM).

    A real-time clock is also included on the board:
    u1: MM58167A

    The GROM address counter is implemented by four 74LS161 chips.

    A 3V battery is used to buffer the SRAMs.

    The 74LS259 latch (u14) stores CRU settings and controls select lines.

    Memory/port mapping
    -------------------
    The card can be configured to respond to CRU addresses 1000-1700 with the
    exception of 1100, which is used by most disk controller cards. The
    address is set by one of seven switches (SW1).

    CRU bits (relative to start address):

    0 - Map DSR into CPU space 4000-5FFF
    1 - Enable GRAM and RAM
    2 - Write protect GRAM/RAM
    3 - DSR/RAM bank selection
    4 - DSR/RAM bank selection (see below)

    The GRAM simulation responds to the usual GROM address base 9800. However,
    only GROMs 3-7 are simulated; GROMs 0-2 are located in the console. Setting
    the GROM address is done by writing both bytes of the 16-bit address to
    address 9C02 (high/low). Reading the address is not possible, but this is
    provided by the console GROMs. Reading the contents of the GRAM simulation
    is done by reading a byte from address 9800, which also advances the address
    counter. Writing to the GRAM is achieved by writing to address 9C00.

    For the PGRAM+ card, four GROM banks are available:
    Bank 0: 98x0/9Cx0/9Cx2
    Bank 1: 98x4/9Cx4/9Cx4
    Bank 2: 98x8/9Cx8/9Cx8
    Bank 3: 98xC/9CxC/9CxC

    Due to incomplete decoding, the second digit from the right may take any
    value.

    The real-time clock is mapped to addresses 8640-865E (even addresses only).
    8640 = Register 0
    865e = Register 15

    The 32K RAM circuit (u23) is used for the DSR (device service routine, i.e.
    the driver) and 16 KiB RAM. The DSR area is located in the lower half of the
    address space, while the RAM area is located in the upper half.

    The DSR area is mapped as two selectable 8K blocks into 4000-5FFF.
    The RAM area is mapped as two selectable 8K blocks into 6000-7FFF.

    +------------+   0000
    | DSR bank 0 |
    +------------+   2000
    | DSR bank 1 |
    +------------+   4000
    | RAM bank 0 |
    +------------+   6000
    | RAM bank 1 |
    +------------+   7FFF

    The selection of either bank of the DSR space is done by writing a value
    to an address of 4000/4004/4008... for selecting bank 0, and to 4002/4006/...
    for selecting bank 1. For the RAM area, the same scheme is applied to
    6000/6004/... and 6002/6006/... The selection is buffered by the flipflop
    u3 for both RAM and DSR, hence it should make no difference whether to
    write to 6000 or to 4000.

    CRU bits 3 and 4 are fed into the PRE* and CLR* inputs of the flipflop.

    Bits 3  4
         0  0    -> not recommended (typically, bit 4 wins)
         0  1    -> set lower 8K
         1  0    -> set higher 8K
         1  1    -> allow switching by write access

    The PGRAM+ card does not allow for expanding this area. This is indeed a
    limitation, as it does not allow us to store several cartridges with
    a GROM and ROM part into the PGRAM+.

    Switch SW2 deactivates the card in case the contents prevent a proper
    startup of the TI console.

    Michael Zapf
    March 2020

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

#include "emu.h"
#include "pgram.h"

#define LOG_WARN       (1U<<1)
#define LOG_WP         (1U<<2)
#define LOG_DSR        (1U<<3)
#define LOG_RAM        (1U<<4)
#define LOG_GRAM       (1U<<5)
#define LOG_GRAMADDR   (1U<<6)
#define LOG_CRU        (1U<<7)
#define LOG_BANK       (1U<<8)

#define VERBOSE ( LOG_GENERAL | LOG_WARN )

#include "logmacro.h"

#define GRAM4567_TAG "u21_gram4567"
#define GRAM3_TAG "u22_gram3"
#define DSRRAM_TAG "u23_dsrram"
#define CLOCK_TAG "u1_rtc"
#define BANKFF_TAG "u3_bankff"
#define CRULATCH_TAG "u14_latch"

#define COUNT0_TAG "u13_counter"
#define COUNT1_TAG "u11_counter"
#define COUNT2_TAG "u12_counter"
#define COUNT3_TAG "u10_counter"

DEFINE_DEVICE_TYPE(TI99_PGRAM, bus::ti99::peb::pgram_device, "ti99_pgram", "PGRAM(+) memory card")

namespace bus::ti99::peb {

pgram_device::pgram_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock):
	  device_t(mconfig, TI99_PGRAM, tag, owner, clock),
	  device_ti99_peribox_card_interface(mconfig, *this),
	  m_gram3(*this, GRAM3_TAG),
	  m_gram4567(*this, GRAM4567_TAG),
	  m_dsrram(*this, DSRRAM_TAG),
	  m_clock(*this, CLOCK_TAG),
	  m_crulatch(*this, CRULATCH_TAG),
	  m_bankff(*this, BANKFF_TAG),
	  m_count0(*this, COUNT0_TAG),
	  m_count1(*this, COUNT1_TAG),
	  m_count2(*this, COUNT2_TAG),
	  m_count3(*this, COUNT3_TAG),
	  m_lowbyte(false)
{
}

/*
    Read access to the card. This comprises reading from the RAM circuit for
    DSR and free space, for GRAM reading, and the real-time clock.

    Decoding is done by the 74ls138 and 74ls139 circuits on the board.
*/
void pgram_device::readz(offs_t offset, uint8_t *value)
{
	if (!m_active) return;

	// GROMs: 1001 1w.. .... ..00
	if ((offset & 0xfc03)==0x9800)
	{
		gram_read((offset>>2)&0x03, value);
	}
	else
	{
		// DSR/RAM: 01.
		if ((offset & 0xc000)==0x4000)
		{
			dsr_ram_read(offset & 0x3fff, value);
		}
		else
		{
			// RTC: 1000 0110 01.. ...0
			if ((offset & 0xffc1)==0x8640)
			{
				uint8_t val = m_clock->read((offset >> 1) & 0x001f);
				*value = val;
			}
		}
	}
}

/*
    Write access to the card. Same comments apply as for the read access.
*/
void pgram_device::write(offs_t offset, uint8_t data)
{
	if (!m_active) return;

	// GROMs: 1001 11.. .... ..a0
	if ((offset & 0xfc01)==0x9c00)
	{
		if ((offset & 0x0002)!=0)
			set_gram_address(data);
		else
			gram_write((offset>>2)&0x03, data);
	}
	else
	{
		// DSR/RAM: 01.
		if ((offset & 0xc000)==0x4000)
			dsr_ram_write(offset & 0x3fff, data);
		else
		{
			// RTC: 1000 0110 01.. ...0
			if ((offset & 0xffc1)==0x8640)
				m_clock->write((offset >> 1) & 0x001f, data);
		}
	}
}

/*
    Read access to the 32K RAM circuit (u23). This one is used to provide the
    DSR (driver) in the memory area 4000-5fff, and to provide free space in the
    area 6000-7fff. A second bank can be selected for both DSR and RAM using
    CRU bits 3 and 4.
*/
void pgram_device::dsr_ram_read(offs_t offset, uint8_t *value)
{
	// ..0. .... .... .... = DSR
	// ..1. .... .... .... = RAM
	bool dsr = ((offset & 0x2000)==0);

	// Note: On power-up, the ls259 latch is reset, and bits 4 and 3 lead 0
	// to both PRE* and CLR* of the 7474. This would normally cause the bank
	// select line to be 1 (unstable).

	// Reset the clock
	m_bankff->clock_w(1);

	// Latch may have changed; update FF asynchronously
	m_bankff->clear_w(m_crulatch->q3_r());
	m_bankff->preset_w(m_crulatch->q4_r());

	offs_t base = (offset & 0x2000)<<1;   // DSR:0000, RAM:4000
	offs_t address = base | (offset & 0x1fff) | (m_bankff->output_r()? 0x2000 : 0);

	if ((dsr && m_crulatch->q0_r()) || (!dsr && m_crulatch->q1_r()))
	{
		*value = m_dsrram->read(address);

		if (address&1)
		{
			uint16_t b0 = m_dsrram->read(address&0xfffe) << 8 | *value;

			if (dsr)
				LOGMASKED(LOG_DSR, "%04x (bank %d) -> %04x\n",(offset&0xfffe)|0x4000, m_bankff->output_r()? 1:0, b0);
			else
				LOGMASKED(LOG_RAM, "%04x (bank %d) -> %04x\n",(offset&0xfffe)|0x4000, m_bankff->output_r()? 1:0, b0);
		}
	}
}

/*
    Write access to the 32K RAM circuit (u23). See above for information about
    the DSR and RAM space.
    A flipflop (7474) is used to control access to the banks of both DSR and
    RAM. The state of the flipflop is controlled by CRU bits 3 and 4. When both
    bits are set, the flipflop can be set or reset by address line 14 (2^1).
*/
void pgram_device::dsr_ram_write(offs_t offset, uint8_t data)
{
	// ..0. .... .... .... = DSR
	// ..1. .... .... .... = RAM
	bool dsr = ((offset & 0x2000)==0);

	if ((dsr && m_crulatch->q0_r()) || (!dsr && m_crulatch->q1_r()))
	{
		// Get the current settings of the flipflop from the latch
		m_bankff->clear_w(m_crulatch->q3_r());
		m_bankff->preset_w(m_crulatch->q4_r());

		int oldff = m_bankff->output_r();

		// A14 is led to the flipflop D input
		m_bankff->d_w((offset & 2)>>1);  // A14
		m_bankff->clock_w(0);

		if (m_crulatch->q2_r()==0)  // not write-protected
		{
			offs_t base = (offset & 0x2000)<<1;
			offs_t address = base | (offset & 0x1fff) | (m_bankff->output_r()? 0x2000 : 0);

			m_dsrram->write(address, data);
			if (base==0)
				LOGMASKED(LOG_DSR, "%04x (bank %d) <- %02x\n", offset|0x4000, m_bankff->output_r()? 1:0, data);
			else
				LOGMASKED(LOG_RAM, "%04x (bank %d) <- %02x\n", offset|0x4000, m_bankff->output_r()? 1:0, data);
		}

		m_bankff->clock_w(1);
		if (m_bankff->output_r() != oldff)
			LOGMASKED(LOG_BANK, "Switch to bank %d (%04x)\n", m_bankff->output_r(), offset|0x4000);
	}
}

/*
    Read access to the GRAM emulation. The GRAM space is split between
    two memory circuits, one for G3 (8K) and one for the other four GRAMs (32K).
    For PGRAM+, we habe 32K + 128K (four banks).
    When the GROM address is outside the area for G3-G7, the access is ignored.
    GRAM access requires setting CRU bit 1 before.
*/
void pgram_device::gram_read(offs_t offset, uint8_t *value)
{
	// Don't let the debugger mess with the GRAM emulation
	if (machine().side_effects_disabled())
	{
		*value = 0;
		return;
	}

	if (m_crulatch->q1_r())
	{
		// Reset the clock
		m_bankff->clock_w(1);

		clock_gram_counter(0);
		uint16_t gaddress = get_gram_address();

		// GROM3: 011. .... .... ....
		// Checked by u9
		if ((gaddress & 0xe000)==0x6000)
		{
			offs_t gfull = (gaddress & 0x1fff) | (m_pgramplus? (offset << 13) : 0);
			*value = m_gram3->read(gfull);
			LOGMASKED(LOG_GRAM, "gram(%04x,%04x) -> %02x\n", gaddress, gfull, *value);
		}

		// GROM4-7: 1... .... .... ....
		// Checked by u9 + u8
		if ((gaddress & 0x8000)!=0)
		{
			offs_t gfull = (gaddress & 0x7fff) | (m_pgramplus? (offset << 15) : 0);
			*value = m_gram4567->read(gfull);
			LOGMASKED(LOG_GRAM, "gram(%04x,%05x) -> %02x\n", gaddress, gfull, *value);
		}
		clock_gram_counter(1);
	}
}

/*
    Write access to the GRAM emulation. Same comments apply as for gram_read.
*/
void pgram_device::gram_write(offs_t offset, uint8_t data)
{
	// Don't let the debugger mess with the GRAM emulation
	if (machine().side_effects_disabled())
	{
		return;
	}

	if (m_crulatch->q1_r())
	{
		clock_gram_counter(0);
		uint16_t gaddress = get_gram_address();

		// GROM3: 011. .... .... ....
		if ((gaddress & 0xe000)==0x6000)
		{
			offs_t gfull = (gaddress & 0x1fff) | (m_pgramplus? (offset << 13) : 0);
			m_gram3->write(gfull, data);
			LOGMASKED(LOG_GRAM, "gram(%04x,%04x) <- %02x\n", gaddress, gfull, data);
		}

		// GROM4-7: 1... .... .... ....
		if ((gaddress & 0x8000)!=0)
		{
			offs_t gfull = (gaddress & 0x7fff) | (m_pgramplus? (offset << 15) : 0);
			m_gram4567->write(gfull, data);
			LOGMASKED(LOG_GRAM, "gram(%04x,%05x) <- %02x\n", gaddress, gfull, data);
		}
		clock_gram_counter(1);
	}
}

/*
    Set the address digits in the counters. Address setting is done by two
    consecutive byte writes, where the currently stored digits in u13 and u11
    are transferred to u12 and u10, and u13 and u11 get their new values
    from the data bus.

    D0-D3 -> u11 -> u10
    D4-D7 -> u13 -> u12
*/
void pgram_device::set_gram_address(uint8_t data)
{
	if (m_crulatch->q1_r())
	{
		set_load_gram_counter(0);
		clock_gram_counter(0);
		m_count0->p_w(data&0x0f);
		m_count1->p_w((data>>4)&0x0f);
		m_count2->p_w(m_count0->output_r());
		m_count3->p_w(m_count1->output_r());
		clock_gram_counter(1);
		set_load_gram_counter(1);
		m_lowbyte = !m_lowbyte;
	}
}

/*
    Set the clock signal for all four counter chips. Propagate carry bits.
*/
void pgram_device::clock_gram_counter(int state)
{
	if (m_crulatch->q1_r())
	{
		m_count1->cet_w(m_count0->tc_r());
		m_count2->cet_w(m_count1->tc_r());
		m_count3->cet_w(m_count2->tc_r());

		m_count0->clock_w(state);
		m_count1->clock_w(state);
		m_count2->clock_w(state);
		m_count3->clock_w(state);

		// Debugging
		if (state==1 && m_lowbyte)
		{
			uint16_t gaddr = get_gram_address();
			if (gaddr >= 0x6000) LOGMASKED(LOG_GRAMADDR, "gaddr=%04x\n", gaddr);
		}
	}
}

/*
    Convenience method to retrieve the current GRAM address.
*/
uint16_t pgram_device::get_gram_address()
{
	return (m_count3->output_r()<<12) | (m_count2->output_r()<<8) | (m_count1->output_r()<<4) | m_count0->output_r();
}

/*
    Set/reset the PE* bit of all 74LS161 curcuits.
*/
void pgram_device::set_load_gram_counter(int state)
{
	m_count0->pe_w(state);
	m_count1->pe_w(state);
	m_count2->pe_w(state);
	m_count3->pe_w(state);
}

/*
    CRU write access to the LS259 latch.
*/
void pgram_device::cruwrite(offs_t offset, uint8_t data)
{
	// ...1 0xxx .... ...0
	bool selected = ((offset & 0xff01)==m_crubase);
	if (selected)
	{
		uint8_t bit = (offset & 0xfe)>>1;
		LOGMASKED(LOG_CRU, "cru %04x (bit %d) <- %d\n", offset, bit , data);
		if (bit==3 || bit==4) LOGMASKED(LOG_BANK, "cru bit %d <- %d\n", bit, data);
	}

	m_crulatch->enable_w(selected? 0 : 1);
	m_crulatch->write_abcd((offset>>1)&0x07, data);
}

/*
    Device construction.
*/
void pgram_device::device_add_mconfig(machine_config& config)
{
	// 4-bit counters
	TTL74161(config, m_count0); //  u13
	TTL74161(config, m_count1); //  u11
	TTL74161(config, m_count2); //  u12
	TTL74161(config, m_count3); //  u10

	// Hardwired connections
	m_count0->set_cet_pin_value(1);

	m_count0->set_cep_pin_value(1);
	m_count1->set_cep_pin_value(1);
	m_count2->set_cep_pin_value(1);
	m_count3->set_cep_pin_value(1);

	// CRU latch
	LS259(config, m_crulatch); // u14

	// Bank switch
	TTL7474(config, m_bankff, 0);

	// We allocate the space for PGRAM+
	BUFF_RAM(config, GRAM4567_TAG, 0).set_size(128*1024);
	BUFF_RAM(config, GRAM3_TAG, 0).set_size(32*1024);
	BUFF_RAM(config, DSRRAM_TAG, 0).set_size(32*1024);

	// Real-time clock
	MM58167(config, CLOCK_TAG, 32.768_kHz_XTAL);
}

void pgram_device::device_start()
{
}

void pgram_device::device_reset()
{
	m_active = (ioport("SW2")->read()==1);
	m_crubase = (ioport("SW1")->read()<<8) | 0x1000;
	m_pgramplus = (ioport("SIZE")->read()==1);
}

INPUT_CHANGED_MEMBER( pgram_device::sw1_changed )
{
	// CRU base changed
	m_crubase = (newval << 8) | 0x1000;
}

INPUT_CHANGED_MEMBER( pgram_device::sw2_changed )
{
	// Activation switch changed
	m_active = (newval==1);
}

INPUT_PORTS_START( pgram_switches )
	PORT_START( "SIZE" )
	PORT_CONFNAME( 0x01, 0x00, "Memory size")  // cannot be changed during runtime
		PORT_CONFSETTING(0x00, "PGRAM 72K")
		PORT_CONFSETTING(0x01, "PGRAM+ 192K")

	PORT_START( "SW2" )
	PORT_DIPNAME( 0x01, 0x01, "Activate switch" ) PORT_CHANGED_MEMBER(DEVICE_SELF, pgram_device, sw2_changed, 0)
		PORT_DIPSETTING(0x00, DEF_STR( Off ))
		PORT_DIPSETTING(0x01, DEF_STR( On ))

	PORT_START( "SW1" )
	PORT_DIPNAME( 0x07, 0x00, "CRU base" ) PORT_CHANGED_MEMBER(DEVICE_SELF, pgram_device, sw1_changed, 0)
		PORT_DIPSETTING( 0x00, "1000")
		PORT_DIPSETTING( 0x02, "1200")
		PORT_DIPSETTING( 0x03, "1300")
		PORT_DIPSETTING( 0x04, "1400")
		PORT_DIPSETTING( 0x05, "1500")
		PORT_DIPSETTING( 0x06, "1600")
		PORT_DIPSETTING( 0x07, "1700")
INPUT_PORTS_END

ioport_constructor pgram_device::device_input_ports() const
{
	return INPUT_PORTS_NAME( pgram_switches );
}

} // end namespace bus::ti99::peb