1 // license:BSD-3-Clause
2 // copyright-holders:Miodrag Milanovic, Robbbert
3 /***************************************************************************
4 
5 MMD-2 driver by Miodrag Milanovic
6 
7 2009-05-12 Initial version
8 2011-01-12 MMD2 working [Robbbert]
9 
10 
11 http://www.cs.unc.edu/~yakowenk/classiccmp/mmd2/
12 Memory map:
13 
14     * 4K RAM addresses $0000..$0FFF
15     * ROM addresses $D800..$E7FF
16     * 256 bytes of RAM, ($FC00..$FCFF?)
17 
18 DIP switches:
19 
20     * WE 0 - Write enable for addresses $0000..$03FF
21     * WE 1 - Write enable for addresses $0400..$07FF
22     * WE 2 - Write enable for addresses $0800..$0BFF
23     * WE 3 - Write enable for addresses $0C00..$0FFF
24     * SPARE - ???
25     * HEX OCT - choose display and entry to be in Hexadecimal or Octal
26     * PUP RESET - ???
27     * EXEC USER - update binary LED's with data entry? Or not?
28       (in either setting, outputs to ports 0,1,2 still show)
29 
30 Operation:
31 
32     * Enter bytes on the keypad of hex digits
33     * Set MSByte of address by entering it on the keypad & pressing "HIGH".
34     * ... LSByte ... "LOW"
35     * Change contents of memory at the selected address by entering the new value & pressing "STORE".
36     * Look at adjacent memory locations with "NEXT" and "PREV".
37     * Execute the program at the selected address by pressing "GO".
38 
39 AUX functions:
40 
41     * BRL HI # - OFF disables BRL LO
42     * BRL LO #
43     * STEP #
44     * SRC HI # - source for COPY/DUMP - OFF disables "DUMP" function
45     * DES HI # - destination for COPY/DUMP
46     * LEN HI # - length for COPY/DUMP
47     * CLR TST ON - test if PROM is empty
48     * POP PRM ON - program a PROM
49     * DUP TST ON - test if PROM duplicated okay
50     * PROM 2708/2716
51     * MEM MAP RAM/ROM
52     * BAUD 110/150/300/600/1200
53 
54 The memory map can be rearranged by the system by using IN5, IN6, IN7.
55 A pair of undumped proms control what goes where on each map.
56 Each set of ROMs also has its own pair of PROMs.
57 
58 
59 I/O ports:
60 IN0: user expansion
61 IN1: 0-TTYIN, 1-CASSIN, 3-SW8(binary/ports), 4-SW7(reset/pup), 5-SW6(hex/oct), 6-(pup signal)
62 IN3: 8279 status
63 IN4: 8279 key
64 IN5: set MAP1
65 IN6: set MAP2
66 IN7: set MAP3
67 IN8: read eprom (in the eprom programmer)
68 OUT0: PORT0 LEDs
69 OUT1: PORT1 LEDs
70 OUT2: PORT2 LEDs
71 OUT3: 8279 control
72 OUT4: 8279 7-segment LED data
73 OUT5: TTYOUT, CASSOUT
74 OUT9: turn pup signal off
75 OUTA: programming pulse on/off (eprom programmer)
76 
77 Dips:
78 SW1-4 hardware control of RAM being writable
79 SW5 not used
80 SW6 Control if the 7-segment displays show Octal (low) or Hex (high).
81 SW7 Reset only causes the cpu to perform a warm start. PUP does a cold start (Power-UP).
82 SW8 Control if the PORT displays echo the 7-segment displays (high), or just act as normal output ports (low).
83 
84 
85 ToDo
86 - Hook up WE0-3
87 - tty rs232 interface
88 - Add interrupt module (INTE LED is always on atm)
89 - Need software
90 - Probably lots of other things
91 
92 ****************************************************************************/
93 
94 #include "emu.h"
95 #include "cpu/i8085/i8085.h"
96 #include "machine/i8279.h"
97 #include "imagedev/cassette.h"
98 #include "speaker.h"
99 #include "mmd2.lh"
100 
101 
102 class mmd2_state : public driver_device
103 {
104 public:
mmd2_state(const machine_config & mconfig,device_type type,const char * tag)105 	mmd2_state(const machine_config &mconfig, device_type type, const char *tag)
106 		: driver_device(mconfig, type, tag)
107 		, m_maincpu(*this, "maincpu")
108 		, m_cass(*this, "cassette")
109 		, m_banks(*this, "bank%u", 1U)
110 		, m_io_keyboard(*this, "X%u", 0)
111 		, m_io_dsw(*this, "DSW")
112 		, m_digits(*this, "digit%u", 0U)
113 		, m_p(*this, "p%u_%u", 0U, 0U)
114 		, m_led_halt(*this, "led_halt")
115 		, m_led_hold(*this, "led_hold")
116 		, m_led_inte(*this, "led_inte")
117 	{ }
118 
119 	void mmd2(machine_config &config);
120 
121 	void init_mmd2();
122 
123 	DECLARE_INPUT_CHANGED_MEMBER(reset_button);
124 
125 private:
126 	virtual void machine_start() override;
127 	virtual void machine_reset() override;
128 	void round_leds_w(offs_t, u8);
129 	void port05_w(u8 data);
130 	u8 port01_r();
131 	u8 port13_r();
132 	u8 bank_r(address_space &space, offs_t offset);
133 	u8 keyboard_r();
134 	void scanlines_w(u8 data);
135 	void digit_w(u8 data);
136 	void status_callback(u8 data);
137 	DECLARE_WRITE_LINE_MEMBER(inte_callback);
138 
139 	void io_map(address_map &map);
140 	void mem_map(address_map &map);
141 	void reset_banks();
142 
143 	u8 m_digit;
144 	std::unique_ptr<u8[]> m_ram;
145 	required_device<i8080_cpu_device> m_maincpu;
146 	required_device<cassette_image_device> m_cass;
147 	required_memory_bank_array<8> m_banks;
148 	required_ioport_array<4> m_io_keyboard;
149 	required_ioport m_io_dsw;
150 	output_finder<9> m_digits;
151 	output_finder<3, 8> m_p;
152 	output_finder<> m_led_halt;
153 	output_finder<> m_led_hold;
154 	output_finder<> m_led_inte;
155 };
156 
157 
round_leds_w(offs_t offset,u8 data)158 void mmd2_state::round_leds_w(offs_t offset, u8 data)
159 {
160 	for (u8 i = 0; i < 8; i++)
161 		m_p[offset][i] = BIT(data, i) ? 0 : 1;
162 }
163 
mem_map(address_map & map)164 void mmd2_state::mem_map(address_map &map)
165 {
166 	map.unmap_value_high();
167 	map(0x0000, 0x03ff).bankr("bank1").bankw("bank2");
168 	map(0x0400, 0x0fff).bankr("bank3").bankw("bank4");
169 	map(0xd800, 0xe3ff).bankr("bank5").bankw("bank6");
170 	map(0xe400, 0xe7ff).bankr("bank7").bankw("bank8");
171 	map(0xfc00, 0xfcff).ram();
172 }
173 
io_map(address_map & map)174 void mmd2_state::io_map(address_map &map)
175 {
176 	map.unmap_value_high();
177 	map(0x00, 0x02).w(FUNC(mmd2_state::round_leds_w));
178 	map(0x01, 0x01).r(FUNC(mmd2_state::port01_r));
179 	map(0x03, 0x03).rw("i8279", FUNC(i8279_device::status_r), FUNC(i8279_device::cmd_w));
180 	map(0x04, 0x04).rw("i8279", FUNC(i8279_device::data_r), FUNC(i8279_device::data_w));
181 	map(0x05, 0x07).r(FUNC(mmd2_state::bank_r));
182 	map(0x05, 0x05).w(FUNC(mmd2_state::port05_w));
183 	//map(0x09, 0x09).w  PUP signal
184 	//map(0x0a, 0x0a).w  Eprom programmer
185 }
186 
187 
188 /* Input ports */
189 static INPUT_PORTS_START( mmd2 )
190 	PORT_START("DSW")
191 	PORT_DIPNAME( 0x20, 0x00, "Sw6") PORT_DIPLOCATION("SW1:1")
192 	PORT_DIPSETTING(    0x20, "Hex")
193 	PORT_DIPSETTING(    0x00, "Octal")
194 	PORT_DIPNAME( 0x10, 0x10, "Sw7") PORT_DIPLOCATION("SW1:2")
195 	PORT_DIPSETTING(    0x10, "PUP")
196 	PORT_DIPSETTING(    0x00, "Reset")
197 	PORT_DIPNAME( 0x08, 0x08, "Sw8") PORT_DIPLOCATION("SW1:3")
198 	PORT_DIPSETTING(    0x08, "Exec")
199 	PORT_DIPSETTING(    0x00, "User")
200 
201 	PORT_START("X0")
PORT_CODE(KEYCODE_F)202 	PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("F") PORT_CODE(KEYCODE_F)
203 	PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("E") PORT_CODE(KEYCODE_E)
204 	PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("D") PORT_CODE(KEYCODE_D)
205 	PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("C") PORT_CODE(KEYCODE_C)
206 	PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("CANCEL") PORT_CODE(KEYCODE_X)
207 	PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("AUX") PORT_CODE(KEYCODE_W)
208 	PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("REGS") PORT_CODE(KEYCODE_R)
209 	PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("MEM") PORT_CODE(KEYCODE_MINUS)
210 	PORT_START("X1")
211 	PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("B") PORT_CODE(KEYCODE_B)
212 	PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("A") PORT_CODE(KEYCODE_A)
213 	PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("9") PORT_CODE(KEYCODE_9)
214 	PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("8") PORT_CODE(KEYCODE_8)
215 	PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("COPY") PORT_CODE(KEYCODE_Y)
216 	PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("PROM") PORT_CODE(KEYCODE_U)
217 	PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("DUMP") PORT_CODE(KEYCODE_I)
218 	PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("LOAD") PORT_CODE(KEYCODE_O)
219 	PORT_START("X2")
220 	PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("7") PORT_CODE(KEYCODE_7)
221 	PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("6") PORT_CODE(KEYCODE_6)
222 	PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("5") PORT_CODE(KEYCODE_5)
223 	PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("4") PORT_CODE(KEYCODE_4)
224 	PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("OPTION") PORT_CODE(KEYCODE_S)
225 	PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("GO") PORT_CODE(KEYCODE_G)
226 	PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("LOW") PORT_CODE(KEYCODE_L)
227 	PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("HIGH") PORT_CODE(KEYCODE_H)
228 	PORT_START("X3")
229 	PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("3") PORT_CODE(KEYCODE_3)
230 	PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("2") PORT_CODE(KEYCODE_2)
231 	PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("1") PORT_CODE(KEYCODE_1)
232 	PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("0") PORT_CODE(KEYCODE_0)
233 	PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("STEP") PORT_CODE(KEYCODE_Z)
234 	PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("NEXT") PORT_CODE(KEYCODE_UP)
235 	PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("STORE") PORT_CODE(KEYCODE_ENTER)
236 	PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_KEYBOARD) PORT_NAME("PREV") PORT_CODE(KEYCODE_DOWN)
237 	PORT_START("RESET")
238 	PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_KEYBOARD) PORT_NAME("RESET") PORT_CODE(KEYCODE_LALT) PORT_CHANGED_MEMBER(DEVICE_SELF, mmd2_state, reset_button, 0)
239 INPUT_PORTS_END
240 
241 INPUT_CHANGED_MEMBER(mmd2_state::reset_button)
242 {
243 	if (newval)
244 		reset_banks();
245 	m_maincpu->set_input_line(INPUT_LINE_RESET, newval ? ASSERT_LINE : CLEAR_LINE);
246 }
247 
248 /*
249 Keyboard
250 0  1  2  3      PREV  STORE  NEXT  STEP
251 4  5  6  7      HIGH  LOW  GO  OPTION
252 8  9  A  B      LOAD  DUMP  PROM  COPY
253 C  D  E  F      MEM  REGS  AUX  CANCEL
254 
255 */
256 
bank_r(address_space & space,offs_t offset)257 u8 mmd2_state::bank_r(address_space &space, offs_t offset)
258 {
259 	for (auto &bank : m_banks)
260 		bank->set_entry(offset);
261 	return space.unmap();
262 }
263 
port01_r()264 u8 mmd2_state::port01_r()
265 {
266 	// need to add ttyin bit 0
267 	u8 data = 0x84;
268 	data |= m_io_dsw->read();
269 	data |= (m_cass->input() < 0.02) ? 0 : 2;
270 	return data;
271 }
272 
port05_w(u8 data)273 void mmd2_state::port05_w(u8 data)
274 {
275 	// need to add ttyout bit 0
276 	m_cass->output(BIT(data, 1) ? -1.0 : +1.0);
277 }
278 
scanlines_w(u8 data)279 void mmd2_state::scanlines_w(u8 data)
280 {
281 	m_digit = data;
282 }
283 
digit_w(u8 data)284 void mmd2_state::digit_w(u8 data)
285 {
286 	if (m_digit < 9)
287 		m_digits[m_digit] = data;
288 }
289 
keyboard_r()290 u8 mmd2_state::keyboard_r()
291 {
292 	u8 data = 0xff;
293 
294 	if ((m_digit & 7) < 4)
295 		data = m_io_keyboard[m_digit & 7]->read();
296 
297 	return data;
298 }
299 
status_callback(u8 data)300 void mmd2_state::status_callback(u8 data)
301 {
302 	// operate the HALT LED
303 	m_led_halt = ~data & i8080_cpu_device::STATUS_HLTA;
304 	// operate the HOLD LED - this should connect to the HLDA pin,
305 	// but it isn't emulated, using WO instead (whatever that does).
306 	m_led_hold = data & i8080_cpu_device::STATUS_WO;
307 }
308 
WRITE_LINE_MEMBER(mmd2_state::inte_callback)309 WRITE_LINE_MEMBER( mmd2_state::inte_callback )
310 {
311 	// operate the INTE LED
312 	m_led_inte = state;
313 }
314 
machine_start()315 void mmd2_state::machine_start()
316 {
317 	m_digits.resolve();
318 	m_p.resolve();
319 	m_led_halt.resolve();
320 	m_led_hold.resolve();
321 	m_led_inte.resolve();
322 	save_pointer(NAME(m_ram), 0x1400);
323 	save_item(NAME(m_digit));
324 }
325 
machine_reset()326 void mmd2_state::machine_reset()
327 {
328 	reset_banks();
329 }
330 
reset_banks()331 void mmd2_state::reset_banks()
332 {
333 	for (auto &bank : m_banks)
334 		bank->set_entry(0);
335 }
336 
init_mmd2()337 void mmd2_state::init_mmd2()
338 {
339 	// We preset all banks here, so that bankswitching will incur no speed penalty.
340 	// ROM 0000/0400 indicate ROMs, RAM /0400/0C00 indicate RAM, 1000 is a dummy write area for ROM banks.
341 	u8 *const ROM = memregion("maincpu")->base();
342 	m_ram = make_unique_clear<u8[]>(0x1400);
343 	u8 *RAM = m_ram.get();
344 	m_banks[0]->configure_entry(0, &ROM[0x0000]);
345 	m_banks[0]->configure_entry(1,  RAM);
346 	m_banks[0]->configure_entry(2, &ROM[0x0c00]);
347 	m_banks[1]->configure_entry(0,  RAM+0x1000);
348 	m_banks[1]->configure_entry(1,  RAM);
349 	m_banks[1]->configure_entry(2,  RAM+0x1000);
350 	m_banks[2]->configure_entry(0, &ROM[0x0400]);
351 	m_banks[2]->configure_entry(1,  RAM+0x0400);
352 	m_banks[2]->configure_entry(2,  RAM+0x0400);
353 	m_banks[3]->configure_entry(0,  RAM+0x1000);
354 	m_banks[3]->configure_entry(1,  RAM+0x0400);
355 	m_banks[3]->configure_entry(2,  RAM+0x0400);
356 	m_banks[4]->configure_entry(0,  RAM);
357 	m_banks[4]->configure_entry(1, &ROM[0x0000]);
358 	m_banks[4]->configure_entry(2, &ROM[0x0000]);
359 	m_banks[5]->configure_entry(0,  RAM);
360 	m_banks[5]->configure_entry(1,  RAM+0x1000);
361 	m_banks[5]->configure_entry(2,  RAM+0x1000);
362 	m_banks[6]->configure_entry(0,  RAM+0x0c00);
363 	m_banks[6]->configure_entry(1, &ROM[0x0c00]);
364 	m_banks[6]->configure_entry(2,  RAM);
365 	m_banks[7]->configure_entry(0,  RAM+0x0c00);
366 	m_banks[7]->configure_entry(1,  RAM+0x1000);
367 	m_banks[7]->configure_entry(2,  RAM);
368 }
369 
mmd2(machine_config & config)370 void mmd2_state::mmd2(machine_config &config)
371 {
372 	/* basic machine hardware */
373 	I8080(config, m_maincpu, 6750000 / 9);
374 	m_maincpu->set_addrmap(AS_PROGRAM, &mmd2_state::mem_map);
375 	m_maincpu->set_addrmap(AS_IO, &mmd2_state::io_map);
376 	m_maincpu->out_status_func().set(FUNC(mmd2_state::status_callback));
377 	m_maincpu->out_inte_func().set(FUNC(mmd2_state::inte_callback));
378 
379 	/* video hardware */
380 	config.set_default_layout(layout_mmd2);
381 
382 	/* Devices */
383 	i8279_device &kbdc(I8279(config, "i8279", 400000));             // based on divider
384 	kbdc.out_sl_callback().set(FUNC(mmd2_state::scanlines_w)); // scan SL lines
385 	kbdc.out_disp_callback().set(FUNC(mmd2_state::digit_w));   // display A&B
386 	kbdc.in_rl_callback().set(FUNC(mmd2_state::keyboard_r));        // kbd RL lines
387 	kbdc.in_shift_callback().set_constant(1);                       // Shift key
388 	kbdc.in_ctrl_callback().set_constant(1);
389 
390 	// Cassette
391 	CASSETTE(config, m_cass);
392 	m_cass->set_default_state(CASSETTE_STOPPED | CASSETTE_SPEAKER_ENABLED | CASSETTE_MOTOR_ENABLED);
393 	SPEAKER(config, "mono").front_center();
394 	m_cass->add_route(ALL_OUTPUTS, "mono", 0.05);
395 }
396 
397 /* ROM definition */
398 ROM_START( mmd2 )
399 	ROM_REGION( 0x2000, "maincpu", 0 )
400 	ROM_LOAD( "mmd2330.bin", 0x0000, 0x0800, CRC(69a77199) SHA1(6c83093b2c32a558c969f4fe8474b234023cc348))
401 	ROM_LOAD( "mmd2340.bin", 0x0800, 0x0800, CRC(70681bd6) SHA1(c37e3cf34a75e8538471030bb49b8aed45d00ec3))
402 	ROM_LOAD( "mmd2350.bin", 0x1000, 0x0800, CRC(359f577c) SHA1(9405ca0c1977721e4540a4017907c06dab08d398))
403 	ROM_LOAD( "mmd2360.bin", 0x1800, 0x0800, CRC(967e69b8) SHA1(c21ec8bef955806a2c6e1b1c8e9068662fb88038))
404 ROM_END
405 
406 /* Driver */
407 
408 //    YEAR  NAME   PARENT  COMPAT  MACHINE  INPUT  CLASS       INIT        COMPANY                FULLNAME  FLAGS
409 COMP( 1976, mmd2,  mmd1,   0,      mmd2,    mmd2,  mmd2_state, init_mmd2,  "E&L Instruments Inc", "MMD-2 Mini-Micro Designer",  MACHINE_NO_SOUND_HW | MACHINE_SUPPORTS_SAVE )
410