summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/drivers/uchroma68.cpp
blob: f57f6df03d723294b1186a83ac21faff87608c08 (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
// license:BSD-3-Clause
// copyright-holders: 68bit
/******************************************************************************

Micro Chroma 68
Motorola evaluation board for the MC6847 VDG and MC1372 RF modulator.

The Micro Chroma was supplied as a kit that included the PCB and the major
parts and is reported to have been designed by the "applications group" in
Austin including Tim Ahrens, Jack Brown, and Hunter Scales. The micro Chroma
68 uses the TVBUG monitor which is a 2k monitor ROM that is included in the
MC6846 timer and is reported to have been written by John Dumas with parts
taken from MIKBUG with the help of Mike Wiles the author of MIKBUG.

References:

1. "Micro Chroma 68: The new 'bug' from Motorola TVBUG 1.2", Motorola
   Semiconductor Products Inc. Microprocessor Operations., 1978

G - Go to user program.
G <addr> - Go to user program, starting at the given address 'addr'.
L - Load tape. The encoding uses the Kansas City Standard and the format is
   largely JBUG compatible. Enter an offset, the difference between the
   existing start address and the target start address. If the data can not be
   stored in the memory then 'Memory Bad' is printed and it exits. The load
   can be aborted by pressing the Escape key.
P - Punch tape. Enter the start address, the end address, a name up to 31
   characters long, and enter return to start. The format is largely JBUG
   compatible, just avoid using 'B' or 'G' in the name. The punch can be
   aborted by pressing the Escape key.
V - Verify Tape. Enter an offset, a difference between the existing start
   address and the target start address. If there is an error then 'Memory
   Bad' is printed and it exits. The verfiy can be aborted by pressing the
   Escape key.
M <addr> - Memory examine and change at address 'addr'.
  <hh><linefeed> - Enter new data for the address and increase the address.
    If the memory can not be changed the 'Memory Bad' is printed that the
    memory examine is exited.
  '^' - decrease the address.
  <carriage return> - exit the memory examine function.
E - Examine a memory block. Enter the start address, the end address, and then
    a space to print the next line.
Q - Quick load. Enter the start address, the end address, then the hex bytes
    to load separate by a space.
F - Fill memory. Enter the start address, the end address, and the fill byte.
O - Offset calculation. Enter the start address and then the end address. The
    offset is printed as a 16 bit number, the lower 8 bits being the offset.
    If the offset is too large then 'TOO FAR' is printed.

R - Register display: CC B A X PC SP. The stack pointer (SP) is stored at
    0xf382 to 0xf383 and may be modifed via the memory examine 'M' function
    there. The other registers are on the stack at:
    SP + 1 : CC
    SP + 2 : B
    SP + 3 : A
    SP + 4,5 : X
    SP + 6,7 : P
Z - Clear screen.
S <addr> - Set a breakpoint at address 'addr'. Up to eight breakpoints may be
    defined. A breakpoint can not be set at 0x0000.
U <addr> - Unset the breakpoint at address 'addr'.
D - Remove all breakpoints.
B - Print all breakpoints.
C - Contine user program, until next breakpoint.
N - Traces one instruction.
T <n> - Traces 'n' instructions.

! - User defined code from 0xf396 to 0xf398, a jump instruction.
" - User defined code from 0xf399 to 0xf39a, a jump instruction.
# - User defined code from 0xf39c to 0xf39d, a jump instruction.

A user input function can be defined at 0xf390 to 0xf392, which is room for a
jump instruction, and this is called by the monitor input loop. This function
should set the carry bit on return if there is user input.

A user output function can be defined at 0xf393 to 0xf395 which is room for a
jump instruction.

The board has a "Reset" button, and a "Break" button that toggled the NMI
line. TODO implement these.

The documentation includes hardware suggestions for memory expansion. This
expands the RAM to use the address range 0x0000 to 0xcfff.

The address range 0xe800 to 0xefff is for user expansion. The documentation
gives examples using this address range for two 1k or one 2k EPROM.

The documentation includes position independent ROMable code for driving a
printer via the ACIA, and it also adds S19 'load', 'punch', and 'verify' tape
support for compatibility with MIKBUG, EXBUG, and MINIBUG. The documentation
suggests using this as an optional ROM at 0xe800 to 0xea73. The printer
support implements a user output function definable at 0xf393 to direct screen
output to the ACIA and with a hardware patch this can be directed to a RS232
printer. The entry point for the printer support is 0xe800. The entry point
for the S19 support is 0xe802, so type 'G E802 <CR>' to enter this option and
it displays a choice of operations. It prints a 'B' for each record 'punched',
and a 'S' for each record loaded or verified, and it reports the address of
any bad memory. The S19 support writes a 0x11 (DC1) to start the tape playback
and a 0x12 (DC2) to start the tape recording as might be expected for the
Texas Instruments 733 ASR twin tape cassette terminal with automatic device
control, but it does not appear to issue DC3 or DC4 to turn the tape off.

The documentation includes patches for a range or TSC software which is
generally supplied in Kansas City S19 format, so might need the above extra
EPROM support. The omission of any mention of support for Motorola software is
notable.

The documentation includes instructions on using the Micro Chroma 68 as a dumb
terminal, requiring some minor hardware modifications to reuse the MC6850 ACIA
for a RS-232 interface, and it includes ROMable code at 0xfc00 to 0xffff code
to implement this, a substitute for the TVBUG monitor. TODO might consider
implementing this, but it was hardly a good terminal for the time.

Address decoding was available in the range 0xc000 to 0xcfff in 1k blocks for
expansion.

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

#include "emu.h"
#include "cpu/m6800/m6800.h"
#include "machine/6821pia.h"
#include "machine/6850acia.h"
#include "machine/mc6846.h"
#include "machine/clock.h"
#include "machine/timer.h"
#include "video/mc6847.h"
#include "imagedev/cassette.h"
#include "sound/wave.h"
#include "speaker.h"

#include "machine/terminal.h"
#include "video/mc6845.h"
#include "screen.h"

#define XTAL_UCHROMA68 3.579545_MHz_XTAL

class uchroma68_state : public driver_device
{
public:
	uchroma68_state(const machine_config &mconfig, device_type type, const char *tag)
		: driver_device(mconfig, type, tag)
		, m_maincpu(*this, "maincpu")
		, m_mc6846(*this, "mc6846")
		, m_mc6847(*this, "mc6847")
		, m_pia(*this, "pia")
		, m_acia(*this, "acia")
		, m_acia_tx_clock(*this, "acia_tx_clock")
		, m_screen(*this, "screen")
		, m_video_ram(*this, "videoram")
		, m_cass(*this, "cassette")
		, m_semi_graphics_six_mod(*this, "SEMI_GRAPHICS_SIX_MOD")
	{ }

	void uchroma68(machine_config &config);

	enum
	{
		TIMER_KBD_STROBE
	};

private:
	required_device<cpu_device> m_maincpu;
	required_device<mc6846_device> m_mc6846;
	required_device<mc6847_ntsc_device> m_mc6847;
	required_device<pia6821_device> m_pia;
	required_device<acia6850_device> m_acia;
	required_device<clock_device> m_acia_tx_clock;
	required_device<screen_device> m_screen;
	required_shared_ptr<uint8_t> m_video_ram;
	required_device<cassette_image_device> m_cass;
	required_ioport m_semi_graphics_six_mod;

	void uchroma68_mem(address_map &map);
	virtual void machine_start() override;
	virtual void machine_reset() override;

	virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;

	DECLARE_READ8_MEMBER(mc6847_videoram_r);
	DECLARE_READ8_MEMBER(pia_pa_r);
	DECLARE_READ8_MEMBER(pia_pb_r);
	DECLARE_WRITE8_MEMBER(mc6846_out_w);
	void kbd_put(uint8_t data);
	emu_timer *m_kbd_strobe_timer;
	bool m_kbd_strobe;
	uint8_t m_kbd_data;
	bool m_video_inv;

	TIMER_DEVICE_CALLBACK_MEMBER(kansas_r);
	DECLARE_WRITE_LINE_MEMBER(kansas_w);
	uint8_t m_cass_rx_clock_count, m_cass_rx_period, m_cass_txcount;
	bool m_cass_in, m_cass_inbit, m_cass_txbit, m_cass_last_txbit;
};


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

    Address Map

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

void uchroma68_state::uchroma68_mem(address_map &map)
{
	// On board user RAM
	map(0x0000, 0x1fff).ram();

	// Off board RAM expansion, as documented.
	map(0x2000, 0xcfff).ram();

	// Display RAM
	map(0xd000, 0xe7ff).ram().share(m_video_ram);

	// Optional EPROM, with the supplied S19 tape format support.
	map(0xe800, 0xebff).rom();

	// Stack RAM
	map(0xf000, 0xf3ff).ram();

	map(0xf408, 0xf409).mirror(0x03b0).rw(m_acia, FUNC(acia6850_device::read), FUNC(acia6850_device::write));

	map(0xf404, 0xf407).mirror(0x03b0).rw(m_pia, FUNC(pia6821_device::read), FUNC(pia6821_device::write));

	// Programmable counter/timer and I/O.
	map(0xf440, 0xf447).mirror(0x0380).rw(m_mc6846, FUNC(mc6846_device::read), FUNC(mc6846_device::write));

	// TVBUG 1.2 ROM
	map(0xf800, 0xffff).rom();
}

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

    Keys

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

static INPUT_PORTS_START(uchroma68)

	// The documentation notes a hardware modification to allow the use of
	// the Semi-graphics 6 mode of the MC6847.
	PORT_START("SEMI_GRAPHICS_SIX_MOD")
	PORT_CONFNAME(0x01, 0x00, "Semi-graphics 6 mode modification")
	PORT_CONFSETTING(0x00, "No")
	PORT_CONFSETTING(0x01, "Yes")

INPUT_PORTS_END


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

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

void uchroma68_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch (id)
	{
	case TIMER_KBD_STROBE:
		m_kbd_strobe = 1;
		m_kbd_data = 0;
		break;
	default:
		throw emu_fatalerror("Unknown id in uchroma68_state::device_timer");
	}
}

WRITE8_MEMBER(uchroma68_state::mc6846_out_w)
{
	m_mc6847->css_w(!BIT(data, 0));
	m_mc6847->intext_w(!BIT(data, 1));
	m_mc6847->ag_w(!BIT(data, 2));
	m_mc6847->gm0_w(BIT(data, 3));
	m_mc6847->gm1_w(BIT(data, 4));
	m_mc6847->gm2_w(BIT(data, 5));
	m_video_inv = BIT(data, 6);
	// P7 is NC.
}


READ8_MEMBER(uchroma68_state::mc6847_videoram_r)
{
	offset &= 0x1fff;
	if (offset > 0x17ff) return 0xff;

	uint8_t code = m_video_ram[offset];

	if (m_semi_graphics_six_mod->read())
		m_mc6847->inv_w(m_video_inv);
	else
		m_mc6847->inv_w(m_video_inv | BIT(code, 6));

	m_mc6847->as_w(BIT(code, 7));

	return code;
}

// TVBUG polls the parallel keyboard strobe every four video frames - it
// blinks the cursor for four frames between polling. At a 60Hz frame rate
// that gives polling at about every 67ms. A 70ms keyboard strobe period
// appears to be more than adequate.
void uchroma68_state::kbd_put(uint8_t data)
{
	m_kbd_data = data;
	m_kbd_strobe_timer->adjust(attotime::from_msec(70));
	m_kbd_strobe = 0;
}

READ8_MEMBER(uchroma68_state::pia_pa_r)
{
	uint8_t data = m_kbd_data;
	return (m_kbd_strobe << 7) | data;
}

READ8_MEMBER(uchroma68_state::pia_pb_r)
{
	// PB0 to PB4 are Up, Down, Left, Right, Home.
	// PB5 is NC
	// PB6 is V VDG
	// PB7 is H VDG
	return (m_mc6847->hs_r() << 7) | (m_mc6847->fs_r() << 6);
}


TIMER_DEVICE_CALLBACK_MEMBER(uchroma68_state::kansas_r)
{
	// Turn 1200/2400Hz to a bit
	uint8_t cassin = (m_cass->input() > +0.04) ? 1 : 0;
	uint8_t inbit = m_cass_inbit;

	m_cass_rx_period++;

	// The RX clock is recovered from the input data, and synchronized to
	// the bit transitions. The software expects a 1x RX clock.
	if (++m_cass_rx_clock_count > 133)
		m_cass_rx_clock_count = 0;

	if (cassin != m_cass_in)
	{
		// A transition, so now check the period.
		inbit = (m_cass_rx_period < 12) ? 1 : 0;
		m_cass_in = cassin;
		m_cass_rx_period = 0;
	}
	else if (m_cass_rx_period > 32)
	{
		// Idle the ACIA if there is no data.
		m_cass_rx_period = 32;
		inbit = 1;
	}

	if (inbit != m_cass_inbit)
	{
		m_acia->write_rxd(inbit);
		m_cass_inbit = inbit;
		// Sync the RX clock with the data edges.
		m_cass_rx_clock_count = 0;
	}

	if (m_cass_rx_clock_count == 0)
		m_acia->write_rxc(0);
	else if (m_cass_rx_clock_count == 67)
		m_acia->write_rxc(1);
}

WRITE_LINE_MEMBER(uchroma68_state::kansas_w)
{
	// The Kansas City cassette format encodes a '0' bit by four cycles of
	// a 1200 Hz sine wave, and a '1' bit as eight cycles of 2400 Hz,
	// giving a 300 baud rate.
	//
	// The clock rate to the ACIA is 16x the baud rate, or 4800Hz, and is
	// divided by 2 to get the 2400 Hz rate, or divided by 4 to get the
	// 1200 Hz rate.

	// Sync the period phase on TX bit transitions.
	if (m_cass_txbit != m_cass_last_txbit)
	{
		m_cass_txcount = 0;
		m_cass_last_txbit = m_cass_txbit;
	}

	if (m_cass_txbit)
		m_cass->output(BIT(m_cass_txcount, 1) ? +1.0 : -1.0); // 2400Hz
	else
		m_cass->output(BIT(m_cass_txcount, 2) ? +1.0 : -1.0); // 1200Hz

	m_cass_txcount++;
	m_acia->write_txc(state);
}

void uchroma68_state::machine_start()
{
	m_kbd_strobe_timer = timer_alloc(TIMER_KBD_STROBE);
	m_kbd_strobe = 1;

	save_item(NAME(m_kbd_strobe));
	save_item(NAME(m_kbd_data));
	save_item(NAME(m_video_inv));
	save_item(NAME(m_cass_rx_clock_count));
	save_item(NAME(m_cass_rx_period));
	save_item(NAME(m_cass_txcount));
	save_item(NAME(m_cass_in));
	save_item(NAME(m_cass_inbit));
	save_item(NAME(m_cass_txbit));
	save_item(NAME(m_cass_last_txbit));
}

void uchroma68_state::machine_reset()
{
	m_mc6846->reset();
	m_pia->reset();
	m_kbd_strobe_timer->reset();
	m_kbd_strobe = 1;
	m_kbd_data = 0;

	// These MC6846 I/O outputs are pulled high. The high impedance state
	// of these lines is not yet emulated by the mc6846 driver, and TVBUG
	// does not drive them. On reset these are high impedance anyway, and
	// pulled high, so their lines going to the VDG are initialized
	// here. The first three lines are inverted.
	m_mc6847->css_w(0);
	m_mc6847->intext_w(0);
	m_mc6847->ag_w(0);
	m_mc6847->gm0_w(1);
	m_mc6847->gm1_w(1);
	m_mc6847->gm2_w(1);
	m_video_inv = 1;

	m_cass_rx_clock_count = 0;
	m_cass_rx_period = 0;
	m_cass_txcount = 0;
	m_cass_in = 0;
	m_cass_inbit = 0;
	m_cass_txbit = 0;
	m_cass_last_txbit = 0;
}

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

    Machine

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

void uchroma68_state::uchroma68(machine_config &config)
{
	M6808(config, m_maincpu, XTAL_UCHROMA68);        // 894.8 kHz clock
	m_maincpu->set_addrmap(AS_PROGRAM, &uchroma68_state::uchroma68_mem);

	MC6846(config, m_mc6846, XTAL_UCHROMA68 / 4);  // Same as the cpu clock
	m_mc6846->out_port().set(FUNC(uchroma68_state::mc6846_out_w));

	SPEAKER(config, "mono").front_center();

	CASSETTE(config, m_cass);
	m_cass->set_default_state(CASSETTE_STOPPED | CASSETTE_MOTOR_ENABLED | CASSETTE_SPEAKER_ENABLED);
	m_cass->add_route(ALL_OUTPUTS, "mono", 0.05);

	SCREEN(config, m_screen, SCREEN_TYPE_RASTER);

	MC6847_NTSC(config, m_mc6847, XTAL_UCHROMA68);
	m_mc6847->set_screen(m_screen);
	m_mc6847->input_callback().set(FUNC(uchroma68_state::mc6847_videoram_r));

	PIA6821(config, m_pia, 0);
	m_pia->readpa_handler().set(FUNC(uchroma68_state::pia_pa_r));
	m_pia->readpb_handler().set(FUNC(uchroma68_state::pia_pb_r));

	ACIA6850(config, m_acia, 0);
	m_acia->txd_handler().set([this] (bool state) { m_cass_txbit = state; });

	CLOCK(config, m_acia_tx_clock, 4800);
	m_acia_tx_clock->signal_handler().set(FUNC(uchroma68_state::kansas_w));

	TIMER(config, "kansas_r").configure_periodic(FUNC(uchroma68_state::kansas_r), attotime::from_hz(40000));

	generic_keyboard_device &keyboard(GENERIC_KEYBOARD(config, "keyboard", 0));
	keyboard.set_keyboard_callback(FUNC(uchroma68_state::kbd_put));
}

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

    ROMS

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

ROM_START(uchroma68)
	ROM_REGION(0x10000,"maincpu",0)
	ROM_LOAD("tvbug.rom", 0xf800, 0x0800, CRC(47f721e4) SHA1(31ea8d596f1f99ee26c4bea694245448bfdc3ee6))
	ROM_LOAD("uch68s19.rom", 0xe800, 0x0400, CRC(8932484f) SHA1(231098ab10185d400032415cf9b7273d736d3b87))
ROM_END

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

  Game driver(s)

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

//    YEAR  NAME    PARENT  COMPAT  MACHINE   INPUT  CLASS        INIT        COMPANY     FULLNAME      FLAGS
COMP( 1980, uchroma68,  0,      0,      uchroma68,    uchroma68, uchroma68_state, empty_init, "Motorola", "Micro Chroma 68" , MACHINE_NO_SOUND_HW )