summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/drivers/ti99_2.cpp
blob: 85604d1068999453ca945180a48500213694e908 (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
// license:GPL-2.0+
// copyright-holders: Michael Zapf
/*
    TI-99/2 driver
    --------------

    Drivers: ti99_224 - 24 KiB version
             ti99_232 - 32 KiB version

    Codenamed "Ground Squirrel", the TI-99/2 was designed to be an extremely
    low-cost, downstripped version of the TI-99/4A, competing with systems
    like the ZX81 or the Times/Sinclair 1000. The targeted price was below $100.

    The 99/2 was equipped with a TMS9995, the same CPU as used in the envisioned
    flag ship 99/8 and later in the Geneve 9640. In the 99/2 it is clocked
    at 10.7 MHz. Also, the CPU has on-chip RAM, unlike the version in the 99/8.

    At many places, the tight price constraint shows up.
    - 2 or 4 KiB of RAM
    - Video memory is part of the CPU RAM
    - Video controller is black/white only and has a fixed character set, no sprites
    - No sound
    - No GROMs (as the significant TI technology circuit)
    - No P-Box connection

    Main board
    ----------
    1 CPU @ 10.7 MHz (contrary to specs which state 5.35 MHz)
    2 RAM circuits   (4 KiB instead of 2 KiB in specs)
    3 or 4 EPROMs
    1 TAL-004 custom gate array as the video controller
    1 TAL-004 custom gate array as the I/O controller
    and six selector or latch circuits

    Connectors
    ----------
    - Built-in RF modulator, switch allows for setting the channel to VHF 3 or 4
    - Two jacks for cassette input and output, no motor control
    - Hexbus connector
    - System expansion slot for cartridges (ROM or RAM), 60 pins, on the back

    Keyboard: 48 keys, no Alpha Lock, two separate Shift keys, additional break key.

    Description
    -----------
    Prototypes were developed in 1983. Despite a late marketing campaign,
    including well-known faces, no devices were ever sold. A few of them
    survived in the hands of the engineers, and were later sold to private
    users.

    The Ground Squirrel underwent several design changes. In the initial
    design, only 2 KiB RAM were planned, and the included ROM was 24 KiB.
    Later, the 2 KiB were obviously expanded to 4 KiB, while the ROM remained
    the same in size. This can be proved here, since the console crashes with
    less than 4 KiB by an unmapped memory access. Also, the CPU is not clocked
    by 5.35 MHz as specified, but by the undivided 10.7 MHz; this was proved
    by running test programs on the real consoles.

    The next version showed an additional 8 KiB of ROM. Possibly in order
    to avoid taking away too much address space, two EPROMs are mapped to the
    same address space block, selectable by a CRU bit. ROM may be added as
    cartridges to be plugged into the expansion slot, and the same is true
    for RAM. Actually, since the complete bus is available on that connector,
    almost any kind of peripheral device could be added. Too bad, none were
    developed.

    However, the Hexbus seemed to have matured in the meantime, which became
    the standard peripheral bus system for the TI-99/8, and for smaller
    systems like the CC-40 and the TI-74. The TI-99/2 also offers a Hexbus
    interface so that any kind of Hexbus device can be connected, like, for
    example, the HX5102 floppy drive, a Wafertape drive, or the RS232 serial
    interface. The 24K version seems to have no proper Hexbus support for
    floppy drives; it always starts the cassette I/O instead.

    The address space layout looks like this:

    0000 - 3FFF:    ROM, unbanked
    4000 - 5FFF:    ROM, banked for 32K version
    6000 - DFFF:    ROM or RAM; ROM growing upwards, RAM growing downwards
    E000 - EFFF:    4K RAM
      EC00 - EEFF:  Area used by video controller (24 rows, 32 columns)
      EF00       :  Control byte for colors (black/white) for backdrop/border
    EF01 - EFFF:    RAM
    F000 - F0FB:    CPU-internal RAM
    F0FC - FFF9:    empty
    FFFA - FFFF:    CPU-internal decrementer and NMI vector

    RAM expansions may be offered via the cartridge port, but they must be
    contiguous with the built-in RAM, which means that it must grow
    downwards from E000. The space from F0FC up to FFF9 may also be covered
    by expansion RAM.

    From the other side, ROM or other memory-mapped devices may occupy
    address space, growing up from 6000.

    One peculiar detail is the memory-mapped video RAM. The video controller
    shares an area of RAM with the CPU. To avoid congestion, the video
    controller must HOLD the CPU while it accesses the video RAM area.

    This decreases processing speed of the CPU, of course. For that reason,
    a special character may be placed in every row after which the row will
    be filled with blank pixels, and the CPU will be released early. This
    means that with a screen full of characters, the processing speed is
    definitely slower than with a clear screen.

    To be able to temporarily get full CPU power, there is a pin VIDENA at
    the video controller which causes the screen to be blank when asserted,
    and to be restored as before when cleared. This is used during cassette
    transfer.

    Technical details
    -----------------
    - HOLD is asserted in every scanline when characters are drawn that are
      not the "Blank End of line" (BEOL). Once encountered, the remaining
      scanline remains blank.
    - During cassette transfer, the screen is blanked using the VIDENA line.
      This is expected and not a bug.
    - When a frame has been completed, the INT4 interrupt of the 9995 is
      triggered as a vblank interrupt.
    - All CRU operations are handled by the second gate array. Unfortunately,
      there is no known documentation about this circuit.
    - The two banks of the last 16 KiB of ROM are selected with the same
      line that is used for selecting keyboard row 0. This should mean that
      you cannot read the keyboard from the second ROM bank.

    I/O map (CRU map)
    -----------------
    0000 - 1DFE: unmapped
    1E00 - 1EFE: TMS9995-internal CRU addresses
    1F00 - 1FD8: unmapped
    1FDA:        TMS9995 MID flag
    1FDC - 1FFF: unmapped
    2000 - DFFE: unmapped
    E000 - E00E: Read: Keyboard column input
    E000 - E00A: Write: Keyboard row selection
    E00C:        Write: unmapped
    E00E:        Write: Video enable (VIDENA)
    E010 - E7FE: Mirrors of the above
    E800 - E80C: Hexbus
       E800 - E806: Data lines
       E808: HSK line
       E80A: BAV line
       E80C: Inhibit (Write only)
    E80E: Cassette

    ROM dumps
    ---------
    Although these machines are extremely rare, we were lucky to get in
    contact with users of both console variants and got dumps from
    their machines.

    The ROMs contain a stripped-down version of TI BASIC, but without
    the specific graphics subprograms. Programs written on the 99/2 should
    run on the 99/4A, but the opposite is not true.

    Original implementation: Raphael Nabet; December 1999, 2000

    Michael Zapf, May 2018

    References :
    [1] TI-99/2 main logic board schematics, 83/03/28-30
    [2] BYTE magazine, June 1983, pp. 128-134
*/

#include "emu.h"
#include "bus/ti99/ti99defs.h"
#include "machine/tms9901.h"
#include "cpu/tms9900/tms9995.h"
#include "bus/ti99/internal/992board.h"
#include "machine/ram.h"
#include "imagedev/cassette.h"
#include "bus/hexbus/hexbus.h"

#define TI992_SCREEN_TAG      "screen"
#define TI992_ROM          "rom_region"
#define TI992_RAM_TAG      "ram_region"
#define TI992_IO_TAG       "io"

#define LOG_WARN           (1U<<1)   // Warnings
#define LOG_CRU            (1U<<2)   // CRU activities
#define LOG_SIGNALS        (1U<<3)   // Signals like HOLD/HOLDA

// Minimum log should be config and warnings
#define VERBOSE ( LOG_GENERAL | LOG_WARN )

#include "logmacro.h"

class ti99_2_state : public driver_device
{
public:
	ti99_2_state(const machine_config &mconfig, device_type type, const char *tag)
		: driver_device(mconfig, type, tag),
		m_maincpu(*this, "maincpu"),
		m_videoctrl(*this, TI992_VDC_TAG),
		m_io992(*this, TI992_IO_TAG),
		m_cassette(*this, TI_CASSETTE),
		m_ram(*this, TI992_RAM_TAG),
		m_otherbank(false),
		m_rom(nullptr),
		m_ram_start(0xf000),
		m_first_ram_page(0)
		{ }

	void ti99_2(machine_config &config);
	void ti99_224(machine_config &config);
	void ti99_232(machine_config &config);

	// Lifecycle
	void driver_start() override;
	void driver_reset() override;

private:
	DECLARE_WRITE8_MEMBER(intflag_write);

	DECLARE_READ8_MEMBER(mem_read);
	DECLARE_WRITE8_MEMBER(mem_write);

	DECLARE_WRITE_LINE_MEMBER(hold);
	DECLARE_WRITE_LINE_MEMBER(holda);
	DECLARE_WRITE_LINE_MEMBER(interrupt);
	DECLARE_WRITE_LINE_MEMBER(cassette_output);

	DECLARE_WRITE_LINE_MEMBER( rombank_set );

	void crumap(address_map &map);
	void memmap(address_map &map);

	required_device<tms9995_device> m_maincpu;
	required_device<bus::ti99::internal::video992_device> m_videoctrl;
	required_device<bus::ti99::internal::io992_device>    m_io992;

	required_device<cassette_image_device> m_cassette;
	required_device<ram_device> m_ram;

	bool m_otherbank;

	uint8_t*   m_rom;
	int m_ram_start;
	int m_first_ram_page;
};

void ti99_2_state::driver_start()
{
	m_rom = memregion(TI992_ROM)->base();
	m_ram_start = 0xf000 - m_ram->default_size();
	m_first_ram_page = m_ram_start >> 12;
}

void ti99_2_state::driver_reset()
{
	m_otherbank = false;

	// Configure CPU to insert 1 wait state for each external memory access
	// by lowering the READY line on reset
	// This has been verified with the real machine, running test loops.
	m_maincpu->ready_line(CLEAR_LINE);
	m_maincpu->reset_line(ASSERT_LINE);
}

WRITE_LINE_MEMBER( ti99_2_state::rombank_set )
{
	m_otherbank = (state==ASSERT_LINE);
}

/*
    Memory map - see description above
*/

void ti99_2_state::memmap(address_map &map)
{
	// 3 or 4 ROMs of 8 KiB. The 32K version has two ROMs mapped into 4000-5fff
	// Part of the ROM is accessed by the video controller for the
	// character definitions (1c00-1fff)
	map(0x0000, 0xffff).rw(FUNC(ti99_2_state::mem_read), FUNC(ti99_2_state::mem_write));
}

/*
    CRU map - see description above
    Note that the CRU address space has only even numbers, and the
    read addresses in the emulation gather 8 bits in one go, so the address
    is the bit number times 16.
*/
void ti99_2_state::crumap(address_map &map)
{
	map(0x0000, 0x0fff).r(m_io992, FUNC(bus::ti99::internal::io992_device::cruread));
	map(0x0000, 0x7fff).w(m_io992, FUNC(bus::ti99::internal::io992_device::cruwrite));

	// Mirror of CPU-internal flags (1ee0-1efe). Don't read. Write is OK.
	map(0x01ee, 0x01ef).nopr();
	map(0x0f70, 0x0f7f).w(FUNC(ti99_2_state::intflag_write));
}

/*
    These CRU addresses are actually inside the 9995 CPU, but they are
    propagated to the outside world, so we can watch the changes.
*/
WRITE8_MEMBER(ti99_2_state::intflag_write)
{
	int addr = 0x1ee0 | (offset<<1);
	switch (addr)
	{
	case 0x1ee0:
		LOGMASKED(LOG_CRU, "Setting 9995 decrementer to %s mode\n", (data==1)? "event" : "timer");
		break;
	case 0x1ee2:
		LOGMASKED(LOG_CRU, "Setting 9995 decrementer to %s\n", (data==1)? "enabled" : "disabled");
		break;
	case 0x1ee4:
		if (data==0) LOGMASKED(LOG_CRU, "Clear INT1 latch\n");
		break;
	case 0x1ee6:
		if (data==0) LOGMASKED(LOG_CRU, "Clear INT3 latch\n");
		break;
	case 0x1ee8:
		if (data==0) LOGMASKED(LOG_CRU, "Clear INT4 latch\n");
		break;
	case 0x1eea:
		LOGMASKED(LOG_CRU, "Switch to bank %d\n", data);
		break;

	default:
		LOGMASKED(LOG_CRU, "Writing internal flag %04x = %d\n", addr, data);
		break;
	}
}

/*
    Called by CPU and video controller.
*/
READ8_MEMBER(ti99_2_state::mem_read)
{
	if (m_maincpu->is_onchip(offset)) return m_maincpu->debug_read_onchip_memory(offset&0xff);

	int page = offset >> 12;

	if (page>=0 && page<4)
	{
		// ROM, unbanked
		return m_rom[offset];
	}
	if (page>=4 && page<6)
	{
		// ROM, banked on 32K version
		if (m_otherbank) offset = (offset & 0x1fff) | 0x10000;
		return m_rom[offset];
	}

	if ((page >= m_first_ram_page) && (page < 15))
	{
		return m_ram->pointer()[offset - m_ram_start];
	}

	LOGMASKED(LOG_WARN, "Unmapped read access at %04x\n", offset);
	return 0;
}

WRITE8_MEMBER(ti99_2_state::mem_write)
{
	int page = offset >> 12;

	if (page>=0 && page<4)
	{
		// ROM, unbanked
		LOGMASKED(LOG_WARN, "Writing to ROM at %04x ignored (data=%02x)\n", offset, data);
		return;
	}

	if (page>=4 && page<6)
	{
		LOGMASKED(LOG_WARN, "Writing to ROM at %04x (bank %d) ignored (data=%02x)\n", offset, m_otherbank? 1:0, data);
		return;
	}

	if ((page >= m_first_ram_page) && (page < 15))
	{
		m_ram->pointer()[offset - m_ram_start] = data;
		return;
	}

	LOGMASKED(LOG_WARN, "Unmapped write access at %04x\n", offset);
}

/*
    Called by the VDC as a vblank interrupt
*/
WRITE_LINE_MEMBER(ti99_2_state::interrupt)
{
	LOGMASKED(LOG_SIGNALS, "Interrupt: %d\n", state);
	m_maincpu->set_input_line(INT_9995_INT4, state);
}

/*
    Called by the VDC to HOLD the CPU
*/
WRITE_LINE_MEMBER(ti99_2_state::hold)
{
	LOGMASKED(LOG_SIGNALS, "HOLD: %d\n", state);
	m_maincpu->hold_line(state);
}

/*
    Called by the CPU to ack the HOLD
*/
WRITE_LINE_MEMBER(ti99_2_state::holda)
{
	LOGMASKED(LOG_SIGNALS, "HOLDA: %d\n", state);
}

void ti99_2_state::ti99_224(machine_config& config)
{
	ti99_2(config);
	// Video hardware
	VIDEO99224(config, m_videoctrl, XTAL(10'738'635));
	m_videoctrl->readmem_cb().set(FUNC(ti99_2_state::mem_read));
	m_videoctrl->hold_cb().set(FUNC(ti99_2_state::hold));
	m_videoctrl->int_cb().set(FUNC(ti99_2_state::interrupt));

	using namespace bus::ti99::internal;
	screen_device& screen(SCREEN(config, TI992_SCREEN_TAG, SCREEN_TYPE_RASTER));
	screen.set_raw(XTAL(10'738'635) / 2, \
			video992_device::TOTAL_HORZ,
			video992_device::HORZ_DISPLAY_START-12,
			video992_device::HORZ_DISPLAY_START + 256 + 12, \
			video992_device::TOTAL_VERT_NTSC, \
			video992_device::VERT_DISPLAY_START_NTSC - 12, \
			video992_device::VERT_DISPLAY_START_NTSC + 192 + 12 );
	screen.set_screen_update(TI992_VDC_TAG, FUNC(video992_device::screen_update));

	// I/O interface circuit. No banking callback.
	IO99224(config, m_io992, 0);
}

void ti99_2_state::ti99_232(machine_config& config)
{
	ti99_2(config);
	// Video hardware
	VIDEO99232(config, m_videoctrl, XTAL(10'738'635));
	m_videoctrl->readmem_cb().set(FUNC(ti99_2_state::mem_read));
	m_videoctrl->hold_cb().set(FUNC(ti99_2_state::hold));
	m_videoctrl->int_cb().set(FUNC(ti99_2_state::interrupt));

	using namespace bus::ti99::internal;
	screen_device& screen(SCREEN(config, TI992_SCREEN_TAG, SCREEN_TYPE_RASTER));
	screen.set_raw(XTAL(10'738'635) / 2, \
			video992_device::TOTAL_HORZ,
			video992_device::HORZ_DISPLAY_START-12,
			video992_device::HORZ_DISPLAY_START + 256 + 12, \
			video992_device::TOTAL_VERT_NTSC, \
			video992_device::VERT_DISPLAY_START_NTSC - 12, \
			video992_device::VERT_DISPLAY_START_NTSC + 192 + 12 );
	screen.set_screen_update(TI992_VDC_TAG, FUNC(video992_device::screen_update));

	// I/O interface circuit
	IO99232(config, m_io992, 0).rombank_cb().set(FUNC(ti99_2_state::rombank_set));
}

void ti99_2_state::ti99_2(machine_config& config)
{
	// TMS9995, standard variant
	// Documents state that there is a divider by 2 for the clock rate
	// Experiments with real consoles proved them wrong.
	TMS9995(config, m_maincpu, XTAL(10'738'635));
	m_maincpu->set_addrmap(AS_PROGRAM, &ti99_2_state::memmap);
	m_maincpu->set_addrmap(AS_IO, &ti99_2_state::crumap);
	m_maincpu->holda_cb().set(FUNC(ti99_2_state::holda));

	// RAM 4 KiB
	// Early documents indicate 2 KiB RAM, but this does not work with
	// either ROM version, so we have to assume that the 2 KiB were never
	// sufficient, not even in the initial design
	RAM(config, TI992_RAM_TAG).set_default_size("4096").set_default_value(0);

	// Cassette drives
	// There is no route from the cassette to some audio input,
	// so we don't hear it.
	CASSETTE(config, "cassette", 0);

	// Hexbus
	HEXBUS(config, TI_HEXBUS_TAG, 0, hexbus_options, nullptr);
}

/*
  ROM loading
*/
ROM_START(ti99_224)
	// The 24K version is an early design; the PCB does not have any socket names
	ROM_REGION(0x6000, TI992_ROM ,0)
	ROM_LOAD("rom0000.bin", 0x0000, 0x2000, CRC(c57436f1) SHA1(71d9048fed0317cfcc4cd966dcbc3bc163080cf9))
	ROM_LOAD("rom2000.bin", 0x2000, 0x2000, CRC(be22c6c4) SHA1(931931d61732bacdab1da227c01b8045ca860f0b))
	ROM_LOAD("rom4000.bin", 0x4000, 0x2000, CRC(926ca20e) SHA1(91624a16aa2c62c7ebc23128308709efdebddca3))
ROM_END

ROM_START(ti99_232)
	ROM_REGION(0x12000, TI992_ROM, 0)
	// The 32K version is a more elaborate design; the ROMs for addresses 0000-1fff
	// and the second bank for 4000-5fff are stacked on the socket U2
	ROM_LOAD("rom0000.u2a", 0x0000, 0x2000, CRC(01b94f06) SHA1(ef2e0c5f0492d7d024ebfe3fad29c2b57ea849e1))
	ROM_LOAD("rom2000.u12", 0x2000, 0x2000, CRC(0a32f80a) SHA1(32ed98481998be295e637eaa2117337cfa4a7984))
	ROM_LOAD("rom4000a.u3", 0x4000, 0x2000, CRC(10c11fab) SHA1(d43e0952538e66e2cedc307b71b65cb388cbe8e3))
	ROM_LOAD("rom4000b.u2b", 0x10000, 0x2000, CRC(34dd52ed) SHA1(e01892b1b110d7d592a7e7f1f39f9f46ea0818db))
ROM_END

//      YEAR    NAME      PARENT    COMPAT  MACHINE   INPUT   CLASS         INIT        COMPANY              FULLNAME                               FLAGS
COMP(   1983,   ti99_224, 0,        0,      ti99_224, 0, ti99_2_state, empty_init, "Texas Instruments", "TI-99/2 BASIC Computer (24 KiB ROM)" , MACHINE_NO_SOUND_HW )
COMP(   1983,   ti99_232, 0,         0,      ti99_232, 0, ti99_2_state, empty_init, "Texas Instruments", "TI-99/2 BASIC Computer (32 KiB ROM)" , MACHINE_NO_SOUND_HW )