// 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'. 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. 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 m_maincpu; required_device m_pia0; required_device m_pia1; required_device m_acia; required_device m_brg; required_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 )