Decoding the MAME Rosetta Stone
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.
Filed under: MAME, RC2014, retro | Leave a Comment
Tags: C++, MAME, RC2014, Z80
No Responses Yet to “Decoding the MAME Rosetta Stone”