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

Motorola Evaluation Kit 6800 D1 - MEK6800D1

The monitor is supplied in the MCM6830L7 ROM and named MIKBUG rev 9. MIKBUG is
a 512 byte monitor. The MIKBUG commands are:

M aaaa - memory examine and change at address 'aaaa'.
  <space>hh - modify content with hex value 'hh' and increase address. If the
              memory can not be changed then it prints '?' and exits the
              memory examine.
  <space> and any other key exits the memory examine. The documentation
          suggests using a carriage return.
  Any other key increases the address.

R - Register display: CC B A X PC SP. The registers can be modified at:
  0xa043  CC
  0xa044  B
  0xa045  A
  0xa046  X high
  0xa047  X low
  0xa048  PC high
  0xa049  PC low
  0xa008  SP high
  0xa009  SP low

L - Loads data, in S19 format, as generated by the assembler. If there is a
  checksum failure then it prints '?' and exits the load. An 'S9' record
  exits the load.  Note that the MIKBUG punch command 'P' does not write a
  'S9' record so it is necessary to manually enter a 'S9' sequence at the
  end of the load when loading a MIKBUG generated tape!

P - Print or Punch memory dump. The start address is loaded from 0xa002 to
  0xa003, and the end address is loaded from 0xa004 to 0xa005. The output is
  in S19 format and can be loaded with the 'L' command, but the end-of-file
  'S9' record is not emitted.

G - Go to user code. The registers are loaded from the addresses noted above
  0xa043 to 0xa049 and 0xa008 to 0xa009 for the stack pointer. Breakpoints
  can be manually set by inserting a SWI (0x3f) instruction, and when these
  are encountered control returns to MIKBUG and the registers are save and
  printed.

An IRQ handler can be defined at 0xa000 to 0xa001.
An NMI handler can be defined at 0xa006 to 0xa007.

MIKBUG was designed to work with the ASR33 Teletypewriter and drives the
'reader control' line high during a 'load' operation intended for a reader
relay control which was a common modification. There is no signal line driven
for a 'punch' operation. TODO might be able to at least use this 'reader
control' line to switch to input from a tape.

MIKBUG was designed to work with the Texas Instruments 733 ASR twin tape
cassette terminal with automatic device control and issues the following
control codes when loading and 'punching' a tape. TODO perhaps these could
be detected and serial I/O diverted to a cassette.
 Code 0x11 DC1 - Tape playback on
 Code 0x12 DC2 - Tape record on.
 Code 0x13 DC3 - Tape playback off.
 Code 0x14 DC4 - Tape record off.

The serial bit rate is controlled by a MC14536 timer and is variable. Common
rates are 110 baud and 300 baud, for the ASR33 and 733 ASR terminals
respectively, but is was practical to run at higher rates up to about 4800
baud.

Memory is expandable off-board with some modifications, to at least 8K. There
were versions of the resident editor and assembler for use with MIKBUG, that
required the expanded RAM.

The board hosts a MC6850 ACIA but this is not used by MIKBUG so it is a user
ACIA. The clock is supplied off board and the documentation offers a design
suggestion using the MC14411 and this is emulated here as documented.

TODO could add a reset button, and an NMI button, and an LED for the reader
relay line. Might want to default the terminal to echo characters.

References:
1. Assembly Instructions for the Motorola M6800 Design Evaluation Kit, 1975.
2. Engineering Note 100, MCM6830L7 MIKBUG/MINIBUG ROM.

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

#include "emu.h"
#include "cpu/m6800/m6800.h"
#include "machine/6821pia.h"
#include "machine/6850acia.h"
#include "machine/mc14411.h"
#include "machine/input_merger.h"
#include "machine/timer.h"
#include "bus/rs232/rs232.h"
#include "machine/terminal.h"

class mekd1_state : public driver_device
{
public:
	mekd1_state(const machine_config &mconfig, device_type type, const char *tag)
		: driver_device(mconfig, type, tag)
		, m_maincpu(*this, "maincpu")
		, m_pia0(*this, "pia0")
		, m_pia1(*this, "pia1")
		, m_acia(*this, "acia")
		, m_brg(*this, "brg")
		, m_rs232(*this, "rs232")
		, m_baud_rate(*this, "BAUD_RATE")
		, m_stop_bits(*this, "STOP_BITS")
		, m_acia_baud_rate(*this, "ACIA_BAUD_RATE")
	{ }

	void mekd1(machine_config &config);

	enum
	{
		TIMER_BIT_RATE,
		TIMER_BIT_RATE_HALF
	};

private:
	virtual void machine_reset() override;
	virtual void machine_start() override;

	void mem_map(address_map &map);

	required_device<cpu_device> m_maincpu;
	required_device<pia6821_device> m_pia0;
	required_device<pia6821_device> m_pia1;
	required_device<acia6850_device> m_acia;
	required_device<mc14411_device> m_brg;
	required_device<rs232_port_device> m_rs232;
	required_ioport m_baud_rate;
	required_ioport m_stop_bits;
	required_ioport m_acia_baud_rate;

	DECLARE_READ8_MEMBER(pia0_pa_r);
	DECLARE_READ8_MEMBER(pia0_pb_r);
	DECLARE_WRITE8_MEMBER(pia0_pa_w);
	DECLARE_WRITE8_MEMBER(pia0_pb_w);
	DECLARE_WRITE_LINE_MEMBER(pia0_cb2_w);

	// Clocks
	DECLARE_WRITE_LINE_MEMBER(write_f1_clock);
	DECLARE_WRITE_LINE_MEMBER(write_f2_clock);
	DECLARE_WRITE_LINE_MEMBER(write_f4_clock);
	DECLARE_WRITE_LINE_MEMBER(write_f5_clock);
	DECLARE_WRITE_LINE_MEMBER(write_f7_clock);
	DECLARE_WRITE_LINE_MEMBER(write_f8_clock);
	DECLARE_WRITE_LINE_MEMBER(write_f9_clock);
	DECLARE_WRITE_LINE_MEMBER(write_f11_clock);
	DECLARE_WRITE_LINE_MEMBER(write_f13_clock);

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

	emu_timer *m_bit_rate_timer;
	emu_timer *m_bit_rate_half_timer;

	bool m_bit_rate_out;
	bool m_bit_rate_half_out;
	bool m_bit_rate_reset;
	bool m_bit_rate_select;
};

void mekd1_state::mem_map(address_map &map)
{
	map.unmap_value_high();
	map(0x0000, 0x027f).mirror(0x7c00).ram();

	// The emulated addressing for these is not exact. The hardware uses
	// A3, A4, and A5 to select between these respectively. For example
	// the first PIA is selected when A3 is high, but it ignores A4 and
	// A5, etc. So it is possible that more than one are selected at once
	// which is not emulated here. Here it is assumed that A3, A4, and A5
	// are completely decoded and that only one device is selected at a
	// time.
	map(0x8004, 0x8007).mirror(0x5fe0).rw(m_pia0, FUNC(pia6821_device::read), FUNC(pia6821_device::write));
	map(0x8008, 0x800b).mirror(0x5fe0).rw(m_pia1, FUNC(pia6821_device::read), FUNC(pia6821_device::write));
	map(0x8010, 0x8011).mirror(0x5fe2).rw(m_acia, FUNC(acia6850_device::read), FUNC(acia6850_device::write));

	map(0xa000, 0xa07f).mirror(0x0f80).ram();

	// Although the ROM is 1k, MIKBUG uses only the first 512 bytes and
	// when using MIKBUG the A9 ROM input is connected to 0V.
	map(0xe000, 0xe1ff).mirror(0x1e00).rom().region("mcm6830l7", 0);
}

static INPUT_PORTS_START( mekd1 )

	PORT_START("BAUD_RATE")
	PORT_CONFNAME(0x3fff, 416, "RS232 Baud Rate")
	PORT_CONFSETTING(9091, "110")
	PORT_CONFSETTING(3333, "300")
	PORT_CONFSETTING(1667, "600")
	PORT_CONFSETTING( 833, "1200")
	PORT_CONFSETTING( 416, "2400")
	PORT_CONFSETTING( 208, "4800")
	PORT_CONFSETTING( 139, "7200")
	PORT_CONFSETTING( 104, "9600")

	PORT_START("STOP_BITS")
	PORT_CONFNAME(0x01, 0, "Stop bits")
	PORT_CONFSETTING(0x00, "1")
	PORT_CONFSETTING(0x01, "2")

	PORT_START("ACIA_BAUD_RATE")
	PORT_CONFNAME(0xf, 1, "ACIA Baud Rate")
	PORT_CONFSETTING(13, "110")
	PORT_CONFSETTING(11, "150")
	PORT_CONFSETTING(9, "300")
	PORT_CONFSETTING(8, "600")
	PORT_CONFSETTING(7, "1200")
	PORT_CONFSETTING(5, "2400")
	PORT_CONFSETTING(4, "3600")
	PORT_CONFSETTING(2, "4800")
	PORT_CONFSETTING(1, "9600")

INPUT_PORTS_END



READ8_MEMBER(mekd1_state::pia0_pa_r)
{
	return m_rs232->rxd_r() << 7;
}

READ8_MEMBER(mekd1_state::pia0_pb_r)
{
	bool timer_out;
	uint8_t stop_bits = m_stop_bits->read();

	if (m_bit_rate_select)
		timer_out = m_bit_rate_out;
	else
		timer_out = m_bit_rate_half_out;

	return (timer_out << 7) | (stop_bits << 6);
}

WRITE8_MEMBER(mekd1_state::pia0_pa_w)
{
	m_rs232->write_txd(BIT(data, 0));
}

WRITE8_MEMBER(mekd1_state::pia0_pb_w)
{
	m_bit_rate_select = BIT(data, 2);

	if (BIT(data, 0) == 1)
	{
		// Reset
		m_bit_rate_timer->reset();
		m_bit_rate_half_timer->reset();
		m_bit_rate_out = 0;
		m_bit_rate_half_out = 0;
		m_bit_rate_reset = 1;
		return;
	}

	if (m_bit_rate_reset)
	{
		// Timer has just been taken out of reset.
		m_bit_rate_reset = 0;
		uint16_t delay = m_baud_rate->read();
		// An adjustment is subtracted from the delay for the CPU
		// instruction processing paths as the CPU polls the timer
		// output, and this would vary with the CPU clock. In
		// hardware, variable resisters needed to be tuned to achieve
		// the correct timing which was not hard at the lower baud
		// rates.
		m_bit_rate_timer->adjust(attotime::from_usec(delay - 31));
		m_bit_rate_half_timer->adjust(attotime::from_usec((delay / 2 ) - 31));
	}
}

WRITE_LINE_MEMBER(mekd1_state::pia0_cb2_w)
{
	// This is a tape reader control line.
}

void mekd1_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch (id)
	{
	case TIMER_BIT_RATE:
		m_bit_rate_out = 1;
		break;
	case TIMER_BIT_RATE_HALF:
		m_bit_rate_half_out = 1;
		break;
	default:
		throw emu_fatalerror("Unknown id in mekd1_state::device_timer");
	}
}


WRITE_LINE_MEMBER(mekd1_state::write_f1_clock)
{
	if (m_acia_baud_rate->read() == 1)
	{
		m_acia->write_txc(state);
		m_acia->write_rxc(state);
	}
}

WRITE_LINE_MEMBER(mekd1_state::write_f2_clock)
{
	if (m_acia_baud_rate->read() == 2)
	{
		m_acia->write_txc(state);
		m_acia->write_rxc(state);
	}
}

WRITE_LINE_MEMBER(mekd1_state::write_f4_clock)
{
	if (m_acia_baud_rate->read() == 4)
	{
		m_acia->write_txc(state);
		m_acia->write_rxc(state);
	}
}

WRITE_LINE_MEMBER(mekd1_state::write_f5_clock)
{
	if (m_acia_baud_rate->read() == 5)
	{
		m_acia->write_txc(state);
		m_acia->write_rxc(state);
	}
}

WRITE_LINE_MEMBER(mekd1_state::write_f7_clock)
{
	if (m_acia_baud_rate->read() == 7)
	{
		m_acia->write_txc(state);
		m_acia->write_rxc(state);
	}
}

WRITE_LINE_MEMBER(mekd1_state::write_f8_clock)
{
	if (m_acia_baud_rate->read() == 8)
	{
		m_acia->write_txc(state);
		m_acia->write_rxc(state);
	}
}

WRITE_LINE_MEMBER(mekd1_state::write_f9_clock)
{
	if (m_acia_baud_rate->read() == 9)
	{
		m_acia->write_txc(state);
		m_acia->write_rxc(state);
	}
}

WRITE_LINE_MEMBER(mekd1_state::write_f11_clock)
{
	if (m_acia_baud_rate->read() == 11)
	{
		m_acia->write_txc(state);
		m_acia->write_rxc(state);
	}
}

WRITE_LINE_MEMBER(mekd1_state::write_f13_clock)
{
	if (m_acia_baud_rate->read() == 13)
	{
		m_acia->write_txc(state);
		m_acia->write_rxc(state);
	}
}


void mekd1_state::machine_reset()
{
	m_pia0->reset();
	m_pia1->reset();
	m_brg->rsa_w(CLEAR_LINE);
	m_brg->rsb_w(ASSERT_LINE);
	m_bit_rate_timer->reset();
	m_bit_rate_half_timer->reset();
	m_bit_rate_out = 0;
	m_bit_rate_half_out = 0;
	m_bit_rate_select = 0;
	m_bit_rate_reset = 1;
}

void mekd1_state::machine_start()
{
	// Allocate timers
	m_bit_rate_timer = timer_alloc(TIMER_BIT_RATE);
	m_bit_rate_half_timer = timer_alloc(TIMER_BIT_RATE_HALF);

	save_item(NAME(m_bit_rate_out));
	save_item(NAME(m_bit_rate_half_out));
	save_item(NAME(m_bit_rate_reset));
	save_item(NAME(m_bit_rate_select));
}

static DEVICE_INPUT_DEFAULTS_START(terminal)
	DEVICE_INPUT_DEFAULTS("RS232_RXBAUD", 0xff, RS232_BAUD_2400)
	DEVICE_INPUT_DEFAULTS("RS232_TXBAUD", 0xff, RS232_BAUD_2400)
	DEVICE_INPUT_DEFAULTS("RS232_STARTBITS", 0xff, RS232_STARTBITS_1)
	DEVICE_INPUT_DEFAULTS("RS232_DATABITS", 0xff, RS232_DATABITS_7)
	DEVICE_INPUT_DEFAULTS("RS232_PARITY", 0xff, RS232_PARITY_SPACE)
	DEVICE_INPUT_DEFAULTS("RS232_STOPBITS", 0xff, RS232_STOPBITS_1)
DEVICE_INPUT_DEFAULTS_END

void mekd1_state::mekd1(machine_config &config)
{
	// The clock rate was adjustable from 100KHz to 1MHz.
	M6800(config, m_maincpu, 2_MHz_XTAL / 2);
	m_maincpu->set_addrmap(AS_PROGRAM, &mekd1_state::mem_map);

	INPUT_MERGER_ANY_HIGH(config, "mainirq").output_handler().set_inputline(m_maincpu, M6800_IRQ_LINE);

	// Terminal I/O.
	// The design uses a 6820 which is slightly different to the emulated 6821.
	// PA0 serial output.
	// PA7 serial input.
	// PB0 resets the rite rate timer when high.
	// PB2 selects the bit rate timer output: 0 half time, 1 full time.
	// PB6 jumper input: 0 for 1 stop bit, 1 for 2 stop bits.
	// PB7 input from the bit rate timer that goes high after the timer period elapses.
	// CB2 "reader control" output
	// IRQA and IRQB are NC.
	PIA6821(config, m_pia0, 0);
	m_pia0->readpa_handler().set(FUNC(mekd1_state::pia0_pa_r));
	m_pia0->readpb_handler().set(FUNC(mekd1_state::pia0_pb_r));
	m_pia0->writepa_handler().set(FUNC(mekd1_state::pia0_pa_w));
	m_pia0->writepb_handler().set(FUNC(mekd1_state::pia0_pb_w));
	m_pia0->cb2_handler().set(FUNC(mekd1_state::pia0_cb2_w));

	// User PIA. All the I/O lines are available at P2.
	PIA6821(config, m_pia1, 0);
	m_pia1->irqa_handler().set("mainirq", FUNC(input_merger_device::in_w<0>));
	m_pia1->irqb_handler().set("mainirq", FUNC(input_merger_device::in_w<1>));

	// User ACIA. Available at P2.
	// /CTS is pulled low, but may be driven.
	// /DCD is pulled low, but may be driven.
	ACIA6850(config, m_acia, 0);
	m_acia->irq_handler().set("mainirq", FUNC(input_merger_device::in_w<2>));

	// Off-board clock for the on-board MC6850.
	MC14411(config, m_brg, XTAL(1'843'200));
	m_brg->out_f<1>().set(FUNC(mekd1_state::write_f1_clock));
	m_brg->out_f<2>().set(FUNC(mekd1_state::write_f2_clock));
	m_brg->out_f<4>().set(FUNC(mekd1_state::write_f4_clock));
	m_brg->out_f<4>().set(FUNC(mekd1_state::write_f5_clock));
	m_brg->out_f<7>().set(FUNC(mekd1_state::write_f7_clock));
	m_brg->out_f<8>().set(FUNC(mekd1_state::write_f8_clock));
	m_brg->out_f<9>().set(FUNC(mekd1_state::write_f9_clock));
	m_brg->out_f<11>().set(FUNC(mekd1_state::write_f11_clock));
	m_brg->out_f<13>().set(FUNC(mekd1_state::write_f13_clock));

	RS232_PORT(config, m_rs232, default_rs232_devices, "terminal");
	m_rs232->set_option_device_input_defaults("terminal", DEVICE_INPUT_DEFAULTS_NAME(terminal));
}


/* ROM definition */
ROM_START( mekd1 )
	ROM_REGION( 0x0200, "mcm6830l7", 0 )
	ROM_LOAD("mikbugv9.bin", 0x0000, 0x0200, CRC(f5ff896f) SHA1(32990115ad9eebe7a1a5a03b4b1ea83360b1820f))
ROM_END

/* Driver */

//    YEAR  NAME    PARENT  COMPAT  MACHINE   INPUT  CLASS        INIT        COMPANY     FULLNAME      FLAGS
COMP( 1975, mekd1, 0,  0,      mekd1,  mekd1, mekd1_state, empty_init, "Motorola", "MEK6800D1", MACHINE_NO_SOUND_HW )