summaryrefslogblamecommitdiffstatshomepage
path: root/src/mame/drivers/mekd1.cpp
blob: 601b9dec45635707e84925400baeeac1e5baacea (plain) (tree)



























































                                                                              
                                   












































                                                                                     


                                                           














































































                                                                                                                 
                               









                                                     
                               



                                           
                                    













































































































































































































































                                                                                                           

                                                 





























                                                                                                                     
// 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 )