Decoding the MAME Rosetta Stone

19Mar22

I mentioned in my previous post Grant Searle Simple Z-80 Machine on MAME that the gsz80.cpp driver was like finding a Rosetta Stone for MAME. In this post I’m going to go through it block by block to explain how I parsed it, and how it enabled me to figure out how to write a driver for the RC2014 (Mini).

// license:BSD-3-Clause
// copyright-holders:Frank Palazzolo

Thanks Frank :)

// MAME Reference driver for Grant Searle's Simple Z80 Computer
// http://www.searle.wales/

And thanks Grant. A key point here is that I was familiar with Grant’s design from my RC2014 tinkering. It’s just about as simple as making a computer can get, using just 7 chips. I’d previously looked at drivers like the Imsai, but couldn’t figure it out. But Grant’s design is clean and simple, and Frank’s code is clean and simple.

For a hardware person (like me) it should be possible to relate the blocks of code to the blocks of hardware. So here goes…

We start off with the includes, which are like a bill of materials for the pieces being used.

// All the common emulator stuff is here
#include "emu.h"

This is the key include, providing all the stuff underlying the other parts. If I was making a Grant Searle system on a breadboard, then this is the bit that would give me the breadboard (and the laws of physics).

// Two member devices referenced in state class, a Z80 and a UART
#include "cpu/z80/z80.h"
#include "machine/6850acia.h"

This gives us two of the main chips, the Z80 CPU and the 6850 UART. The other two main chips are RAM and ROM, which we’ll get to later, and there are also 3 ‘glue’ chips, which we don’t actually need when doing things in software.

// Two more devices needed, a clock device for the UART, and RS-232 devices
#include "machine/clock.h"
#include "bus/rs232/rs232.h"

The system has a clock using a crystal and one of the glue chips, and input/output happens with a terminal attached to an RS232 port.

// State class - derives from driver_device
class gsz80_state : public driver_device

We’re creating a driver, which is done by creating a class based on driver_device (coming from that emu.h include earlier).

{
public:
	gsz80_state(const machine_config &mconfig, device_type type, const char *tag)
		: driver_device(mconfig, type, tag)
		, m_maincpu(*this, "maincpu")   // Tag name for Z80 is "maincpu"
		, m_acia(*this, "acia")         // Tag name for UART is "acia"
	{ }

	// This function sets up the machine configuration
	void gsz80(machine_config &config);

The public methods for the class are essentially plugging the CPU and UART into the emulation ‘breadboard’.

private:
	// address maps for program memory and io memory
	void gsz80_mem(address_map &map);
	void gsz80_io(address_map &map);

	// two member devices required here
	required_device<cpu_device> m_maincpu;
	required_device<acia6850_device> m_acia;
};

and with the private methods we’re declaring that there will be a memory map, an I/O map and getting specific about the CPU and UART that are being used.

// Trivial memory map for program memory
void gsz80_state::gsz80_mem(address_map &map)
{
	map(0x0000, 0x1fff).rom();
	map(0x2000, 0xffff).ram();
}

The memory map uses the first 8K for ROM and the remaining 56K for RAM.

void gsz80_state::gsz80_io(address_map &map)
{
	map.global_mask(0xff);  // use 8-bit ports
	map.unmap_value_high(); // unmapped addresses return 0xff
	map(0x80, 0xbf).rw("acia", FUNC(acia6850_device::read), FUNC(acia6850_device::write));
}

The 6850 is mapped into an I/O address (which in hardware would be done by those ‘glue’ chips).

// This is here only to configure our terminal for interactive use
static DEVICE_INPUT_DEFAULTS_START( terminal )
	DEVICE_INPUT_DEFAULTS( "RS232_RXBAUD", 0xff, RS232_BAUD_115200 )
	DEVICE_INPUT_DEFAULTS( "RS232_TXBAUD", 0xff, RS232_BAUD_115200 )
	DEVICE_INPUT_DEFAULTS( "RS232_DATABITS", 0xff, RS232_DATABITS_8 )
	DEVICE_INPUT_DEFAULTS( "RS232_PARITY", 0xff, RS232_PARITY_NONE )
	DEVICE_INPUT_DEFAULTS( "RS232_STOPBITS", 0xff, RS232_STOPBITS_1 )
DEVICE_INPUT_DEFAULTS_END

Connection parameters for the emulated terminal being connected to the RS232 interface provided by the 6850 UART to give input and output.

Now we wire the parts together:

void gsz80_state::gsz80(machine_config &config)
{
	/* basic machine hardware */

	// Configure member Z80 (via m_maincpu)
	Z80(config, m_maincpu, XTAL(7'372'800));
	m_maincpu->set_addrmap(AS_PROGRAM, &gsz80_state::gsz80_mem);
	m_maincpu->set_addrmap(AS_IO, &gsz80_state::gsz80_io);

The clock speed is set for the Z80 CPU, and it’s connected to the address map, where it will find the ROM and RAM, and the I/O map, where it will find the 6850 UART.

	// Configure UART (via m_acia)
	ACIA6850(config, m_acia, 0);
	m_acia->txd_handler().set("rs232", FUNC(rs232_port_device::write_txd));
	m_acia->rts_handler().set("rs232", FUNC(rs232_port_device::write_rts));
	m_acia->irq_handler().set_inputline("maincpu", INPUT_LINE_IRQ0); // Connect interrupt pin to our Z80 INT line

The UART is wired up to the CPU and RS232 interface.

	// Create a clock device to connect to the transmit and receive clock on the 6850
	clock_device &acia_clock(CLOCK(config, "acia_clock", 7'372'800));
	acia_clock.signal_handler().set("acia", FUNC(acia6850_device::write_txc));
	acia_clock.signal_handler().append("acia", FUNC(acia6850_device::write_rxc));

The UART is also connected to a clock (at the same frequency as the CPU, because in hardware they’d use the same clock signal).

	// Configure a "default terminal" to connect to the 6850, so we have a console
	rs232_port_device &rs232(RS232_PORT(config, "rs232", default_rs232_devices, "terminal"));
	rs232.rxd_handler().set(m_acia, FUNC(acia6850_device::write_rxd));
	rs232.dcd_handler().set(m_acia, FUNC(acia6850_device::write_dcd));
	rs232.cts_handler().set(m_acia, FUNC(acia6850_device::write_cts));
	rs232.set_option_device_input_defaults("terminal", DEVICE_INPUT_DEFAULTS_NAME(terminal)); // must be below the DEVICE_INPUT_DEFAULTS_START block
}

Finally the terminal is connected to the RS232 interface.

// ROM mapping is trivial, this binary was created from the HEX file on Grant's website
ROM_START(gsz80)
	ROM_REGION(0x2000, "maincpu",0)
	ROM_LOAD("gsz80.bin",   0x0000, 0x2000, CRC(6f4bc7e5) SHA1(9008fe3b9754ec5537b3ad90f748096602ba008e))
ROM_END

We said earlier that there would be 8K of ROM at the start of the memory map, here’s where we describe which ROM will be plugged in.

// This ties everything together
//    YEAR  NAME         PARENT    COMPAT  MACHINE   INPUT    CLASS        INIT           COMPANY           FULLNAME                FLAGS
COMP( 201?, gsz80,       0,        0,      gsz80,    0,       gsz80_state, empty_init,    "Grant Searle",   "Simple Z-80 Machine",  MACHINE_NO_SOUND_HW )

The driver definition concludes with a line describing the machine being emulated in terms of its ROMs, machine definition, input (not used), state class, and initialisation along with some biographical details.

And that’s it. The 4 main ICs, and how they’re wired together can be fairly easily picked out of the code. In a follow up post I’ll run through what changed to make an RC2014 driver.



No Responses Yet to “Decoding the MAME Rosetta Stone”

  1. Leave a Comment

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.